├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── federation-token.php ├── phpunit.xml.dist ├── psalm.xml ├── src ├── Builder.php ├── Contracts │ └── StrategyInterface.php ├── Credentials.php ├── Events │ └── TokenCreated.php ├── Exceptions │ ├── Exception.php │ ├── HttpException.php │ ├── InvalidArgumentException.php │ └── InvalidConfigException.php ├── FederationToken.php ├── Manager.php ├── QcloudFederationTokenServiceProvider.php ├── Statement.php ├── Strategies │ ├── BuildTokens.php │ ├── StackStrategy.php │ └── Strategy.php └── Token.php └── tests ├── CredentialsTest.php ├── FeatureTest.php ├── StrategyTest.php ├── TestCase.php └── TokenTest.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [ overtrue ] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | phpcs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup PHP environment 15 | uses: shivammathur/setup-php@v2 16 | - name: Install dependencies 17 | run: composer install 18 | - name: PHPCSFixer check 19 | run: composer check-style 20 | phpunit: 21 | strategy: 22 | matrix: 23 | php_version: [8.1, 8.2] 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Setup PHP environment 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: ${{ matrix.php_version }} 31 | coverage: xdebug 32 | - name: Install dependencies 33 | run: composer install 34 | - name: PHPUnit check 35 | run: ./vendor/bin/phpunit --coverage-text 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | /vendor/ 4 | node_modules/ 5 | npm-debug.log 6 | yarn-error.log 7 | 8 | # Laravel 4 specific 9 | bootstrap/compiled.php 10 | app/storage/ 11 | 12 | # Laravel 5 & Lumen specific 13 | public/storage 14 | public/hot 15 | 16 | # Laravel 5 & Lumen specific with changed public path 17 | public_html/storage 18 | public_html/hot 19 | 20 | storage/*.key 21 | .env 22 | Homestead.yaml 23 | Homestead.json 24 | /.vagrant 25 | .phpunit.result.cache 26 | 27 | cghooks.lock 28 | .php_cs.cache 29 | .php-cs-fixer.cache 30 | composer.lock 31 | .phpunit.cache 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 overtrue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel 腾讯云联合身份临时访问凭证生成器 2 | --- 3 | 4 | ![Laravel Octane Ready Status](https://img.shields.io/badge/Octance-ready-green?style=flat-square) 5 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/overtrue/laravel-qcloud-federation-token?style=flat-square) 6 | ![GitHub License](https://img.shields.io/github/license/overtrue/laravel-qcloud-federation-token?style=flat-square) 7 | ![Packagist Downloads](https://img.shields.io/packagist/dt/overtrue/laravel-qcloud-federation-token?style=flat-square) 8 | 9 | Laravel [腾讯云联合身份临时访问凭证](https://cloud.tencent.com/document/product/1312/48195) 生成器,主要用于下发腾讯云联合身份临时访问凭证,比如前端直传等场景。 10 | 11 | [![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me-button-s.svg?raw=true)](https://github.com/sponsors/overtrue) 12 | 13 | 开始之前,请您仔细阅读并理解一下官方文档: 14 | 15 | - [获取联合身份临时访问凭证](https://cloud.tencent.com/document/product/1312/48195) 16 | - [COS API 授权策略使用指引](https://cloud.tencent.com/document/product/436/31923) 17 | - [条件键说明及使用示例](https://cloud.tencent.com/document/product/436/71307) 18 | - [CAM 策略语法](https://cloud.tencent.com/document/product/598/10603) 19 | - [临时证书](https://cloud.tencent.com/document/api/1312/48198#Credentials) 20 | - [API Doctor(使用诊断)](https://console.cloud.tencent.com/api/diagnosis) 21 | - [COS 自助诊断工具](https://console.cloud.tencent.com/cos/diagnose) 22 | 23 | ## 安装 24 | 25 | ```shell 26 | $ composer require overtrue/laravel-qcloud-federation-token -vvv 27 | ``` 28 | 29 | ### 配置 30 | 31 | 你可以通过以下命令将配置文件写入 `config/federation-token.php`: 32 | 33 | ```php 34 | $ php artisan vendor:publish --provider="Overtrue\\LaravelQCloudFederationToken\\QCloudFederationTokenServiceProvider" 35 | ``` 36 | 37 | **config/federation-token.php** 38 | 39 | ```php 40 | return [ 41 | // 默认配置,strategies 下的每一个策略将合并此基础配置 42 | 'default' => [ 43 | 'secret_id' => env('QCLOUD_COS_SECRET_ID', ''), 44 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY', ''), 45 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'), 46 | "effect" => "allow", 47 | 48 | // 全局变量,会被替换到所有策略中 49 | 'variables' => [ 50 | 'uid' => env('QCLOUD_COS_APP_ID'), 51 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'), 52 | //... 53 | ], 54 | ], 55 | // strategies 56 | 'strategies' => [ 57 | // 策略名称,比如:image/avatar... 58 | 'avatar' => [ 59 | // 策略名称,可选 60 | 'name' => 'avatar', 61 | 62 | // 临时凭证过期时间 63 | 'expires_in' => 1800, 64 | 65 | // 将与默认配置合并 66 | 'variables' => [ 67 | 'appid' => env('QCLOUD_COS_APP_ID'), 68 | 'bucket' => env('QCLOUD_COS_BUCKET', ''), 69 | //... 70 | ], 71 | 72 | // Statement 请参考:https://cloud.tencent.com/document/product/598/10603 73 | "statements" => [ 74 | [ 75 | "action" => [ 76 | // 这里建议不要随便修改,分片上传需要用到这些 77 | 'cos:ListParts', 78 | 'cos:PutObject', 79 | 'cos:PostObject', 80 | 'cos:InitiateMultipartUpload', 81 | 'cos:UploadPart', 82 | 'cos:CompleteMultipartUpload', 83 | 'cos:AbortMultipartUpload', 84 | 'cos:ListMultipartUploads', 85 | ], 86 | "resource" => [ 87 | "qcs::cos:ap-beijing:uid/:-///*", 88 | ], 89 | ] 90 | ], 91 | ], 92 | ], 93 | ]; 94 | ``` 95 | 96 | 97 | 配置语法请参考:https://cloud.tencent.com/document/product/598/10603 98 | 99 | 你可以根据使用场景配置多个策略,然后按策略分发访问凭证。 100 | 101 | > **Warning** 102 | > 103 | > 注意:`condition` 中使用 `cos:content-type` 记得 urlencode, 不然遇到带+号的 MIME 将会失效,比如 `image/svg+xml`。 104 | 105 | ### 安全提醒 106 | 107 | 临时 token 规则配置不合理,可能会造成安全问题,请谨慎配置,请严格检查如下: 108 | 109 | - 配置前请仔细阅读:[《用于前端直传 COS 的临时密钥安全指引》](https://cloud.tencent.com/document/product/436/40265) 110 | - 策略有限制最大长度,不建议把策略配置得太多。 111 | - 凭证生命周期不要设置过长。 112 | - 避免使用主账号来生成凭证,专用的限制登录的 API 子账号。 113 | - 按照[《条件键说明及使用示例》](https://cloud.tencent.com/document/product/436/71307) 添加请求限制如上传大小和类型等。 114 | - 将账号权限控制在尽量小的范围内,避免越权操作。 115 | 116 | ### 变量替换 117 | 118 | 在配置中难免会用到各种上下文变量或者一些动态 resouce 路径等,你可以在配置中指定 `variables` 变量来实现变量替换,例如: 119 | 120 | > 仅 principal 和 resource 中的变量可以替换,其他变量不支持替换。 121 | 122 | ```php 123 | // config/federation-token.php 124 | [ 129 | 'secret_id' => env('QCLOUD_COS_SECRET_ID'), 130 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY'), 131 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'), 132 | "effect" => "allow", 133 | 134 | // 全局变量,会被替换到所有策略中 135 | 'variables' => [ 136 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'), 137 | //... 138 | ], 139 | ], 140 | // strategies 141 | 'strategies' => [ 142 | // 请参考:https://cloud.tencent.com/document/product/598/10603 143 | 'avatar' => [ 144 | // 将与默认配置合并 145 | 'variables' => [ 146 | 'appid' => env('QCLOUD_COS_APP_ID'), 147 | 'bucket' => env('QCLOUD_COS_BUCKET'), 148 | //... 149 | ], 150 | "statements" => [ 151 | [ 152 | "action" => [ 153 | 'cos:ListParts', 154 | 'cos:PutObject', 155 | 'cos:PostObject', 156 | 'cos:InitiateMultipartUpload', 157 | 'cos:UploadPart', 158 | 'cos:CompleteMultipartUpload', 159 | 'cos:AbortMultipartUpload', 160 | 'cos:ListMultipartUploads', 161 | ], 162 | "resource" => [ 163 | "qcs::cos:ap-beijing:uid/:-///*", 164 | ], 165 | ] 166 | ], 167 | ], 168 | ], 169 | ]; 170 | ``` 171 | 172 | 以上配置将会生成如下结果: 173 | 174 | ```json 175 | { 176 | "effect": "allow", 177 | "action": [ 178 | "cos:PutObject", 179 | "cos:GetObject", 180 | ], 181 | "resource": [ 182 | "qcs::cos:ap-beijing:uid/12278900:example-12278900/20220202/bbeae9bb-d650-46f9-aab3-f4171a1bfdea/*" 183 | ] 184 | } 185 | ``` 186 | 187 | ### 内置变量如下 188 | 189 | - `` - UUID 例如:`ca007813-4a49-4d5a-afab-abae18a969a5` 190 | - `` - 当前时间戳,例如:`1654485526` 191 | - `` - 随机字符串,16 位,例如:`Bbq6gkXXIPyCDsEL` 192 | - `` - 随机字符串,32 位,例如:`FykbMqi6GT6JHiyv6E2xqUeo3CZLPjo7` 193 | - `` - 日期,例如:`20220606` 194 | - `` - 日期,例如:`20220606` 195 | - `` - 日期时间(年月日时分秒),例如:`20220606031846` 196 | - `` - 年,例如:`2022` 197 | - `` - 月,例如:`06` 198 | - `` - 日,例如:`06` 199 | - `` - 时,例如:`03` 200 | - `` - 分,例如:`18` 201 | - `` - 秒,例如:`46` 202 | 203 | ## 使用 204 | 205 | ```php 206 | use Overtrue\LaravelQCloudFederationToken\FederationToken; 207 | 208 | // 使用默认策略(配置项 strategies 中第一个) 209 | $token = FederationToken::createToken(); 210 | 211 | // 或者指定策略 212 | $token = FederationToken::strategy('avatar')->createToken(); 213 | 214 | $token->toArray(); 215 | 216 | // 'credentials' => [ 217 | // 'token' => 'kTRtHpOSOCUzTVWmzlPKweHffXjT9Izo7b61a142d6b56d31c0a7ace4d22bcff3zpbsXKTIrCo43dRRh7bDIKE1ZOE1KRYHEm0KNLjWG_aSF63YoQWchg', 218 | // 'tmp_secret_id' => 'AKIDw7dwZbmFSup9CnAOraJ7skiPMybaV3WPP5B4oVMCIL5kLyphV_3IyAHFJ5QMCjE6', 219 | // 'tmp_secret_key' => '/lvEo280/AlGt4orjDl9tWLIOMl5nkexS5Pg+xys7ps=', 220 | // ], 221 | // 'expired_at' => 1547696355, 222 | ``` 223 | 224 | 如果你需要获取生成 token 的配置信息,你可以使用 `getStatements` 方法: 225 | 226 | ```php 227 | $token->getStatements(); 228 | ``` 229 | 230 | 格式请参考: https://cloud.tencent.com/document/product/1312/48195 231 | 232 | ### 字符限制问题 233 | 234 | 对于单次策略请求,官方限制策略总字符数为 1000 字符,稍微复杂一点就会超过这个限制,建议做法: 235 | 236 | - 在控制台配置策略,写法一样,也有可视化配置界面,不限制字符数 237 | - 在 API 侧配置简单的基础策略 238 | 239 | 请求时将会产生交集结果,所以二者结合,既保证了安全,也避免了 API 断字符限制问题。 240 | 241 | ### 事件 242 | 243 | | **Event** | **Description** | 244 | |-------------------------------------------------------------|--------------| 245 | | `Overtrue\LaravelQcloudFederationToken\Events\TokenCreated` | token 生成时触发 | 246 | 247 | ## :heart: 赞助我 248 | 249 | [![Sponsor me](https://github.com/overtrue/overtrue/blob/master/sponsor-me.svg?raw=true)](https://github.com/sponsors/overtrue) 250 | 251 | 如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue) 252 | 253 | ## 贡献代码 254 | 255 | 你可以通过以下方式参与贡献: 256 | 257 | 1. 通过 [issue tracker](https://github.com/overtrue/laravel-package/issues) 提交 Bug; 258 | 2. 通过 [issue tracker](https://github.com/overtrue/laravel-package/issues) 回答问题或修复 Bug; 259 | 3. 通过 Pull Request 增加新特性或优化文档。 260 | 261 | _代码贡献过程不需要很正式。你只需要确保你遵循 PSR-0、PSR-1 和 PSR-2 的编码准则。任何新的代码贡献都必须附带对应的单元测试。_ 262 | 263 | ## Project supported by JetBrains 264 | 265 | Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects. 266 | 267 | [![](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://www.jetbrains.com/?from=https://github.com/overtrue) 268 | 269 | ## PHP 扩展包开发 270 | 271 | > 想知道如何从零开始构建 PHP 扩展包? 272 | > 273 | > 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package) 274 | 275 | ## License 276 | 277 | MIT 278 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overtrue/laravel-qcloud-federation-token", 3 | "description": "QCloud COS FederationToken generator for Laravel.", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "overtrue", 8 | "email": "anzhengchao@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "laravel/framework": "^12.0", 13 | "tencentcloud/tencentcloud-sdk-php": "^3.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Overtrue\\LaravelQcloudFederationToken\\": "src" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Tests\\": "tests" 23 | } 24 | }, 25 | "require-dev": { 26 | "mockery/mockery": "^1.2", 27 | "phpunit/phpunit": "^10.0", 28 | "orchestra/testbench": "^8.0", 29 | "jetbrains/phpstorm-attributes": "^1.0", 30 | "brainmaestro/composer-git-hooks": "dev-master", 31 | "laravel/pint": "^1.5" 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "Overtrue\\LaravelQcloudFederationToken\\QcloudFederationTokenServiceProvider" 37 | ] 38 | }, 39 | "hooks": { 40 | "pre-commit": [ 41 | "composer check-style", 42 | "composer test" 43 | ], 44 | "pre-push": [ 45 | "composer test" 46 | ] 47 | } 48 | }, 49 | "scripts": { 50 | "post-update-cmd": [ 51 | "cghooks remove", 52 | "cghooks add --ignore-lock", 53 | "cghooks update" 54 | ], 55 | "post-merge": "composer install", 56 | "post-install-cmd": [ 57 | "cghooks remove", 58 | "cghooks add --ignore-lock", 59 | "cghooks update" 60 | ], 61 | "cghooks": "vendor/bin/cghooks", 62 | "check-style": "vendor/bin/pint --test", 63 | "fix-style": "vendor/bin/pint", 64 | "test": "phpunit --colors" 65 | }, 66 | "scripts-descriptions": { 67 | "test": "Run all tests.", 68 | "check-style": "Run style checks (only dry run - no fixing!).", 69 | "fix-style": "Run style checks and fix violations." 70 | }, 71 | "config": { 72 | "allow-plugins": { 73 | "composer/package-versions-deprecated": true 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/federation-token.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'secret_id' => env('QCLOUD_COS_SECRET_ID'), 7 | 'secret_key' => env('QCLOUD_COS_SECRET_KEY'), 8 | 'region' => env('QCLOUD_COS_REGION', 'ap-guangzhou'), 9 | 'effect' => 'allow', 10 | 'action' => [ 11 | 'cos:ListParts', 12 | 'cos:PutObject', 13 | 'cos:PostObject', 14 | 'cos:InitiateMultipartUpload', 15 | 'cos:UploadPart', 16 | 'cos:CompleteMultipartUpload', 17 | 'cos:AbortMultipartUpload', 18 | 'cos:ListMultipartUploads', 19 | ], 20 | 'variables' => [ 21 | 'bucket' => env('QCLOUD_COS_BUCKET'), 22 | 'appid' => env('QCLOUD_COS_APP_ID'), 23 | ], 24 | ], 25 | // 请注意: 26 | // 官方限制了一个策略规则的总字符长度为 1000,所以一个规则尽量别写太复杂,否则可能会报错 27 | 'strategies' => [ 28 | // 多个策略生成一个 token 29 | // 'all' => [ 30 | // 'strategies' => ['images'], 31 | // ], 32 | // 'images' => [ 33 | // // 策略名称,可选 34 | // 'name' => 'cos-put', 35 | // // 临时凭证过期时间 36 | // 'expires_in' => 1800, 37 | // 38 | // // 将与默认配置合并 39 | // 'variables' => [ 40 | // 'appid' => env('QCLOUD_APP_ID'), 41 | // 'bucket' => env('QCLOUD_COS_BUCKET', ''), 42 | // //... 43 | // ], 44 | // 45 | // //Statement 请参考:https://cloud.tencent.com/document/product/598/10603 46 | // "statements" => [ 47 | // [ 48 | // "action" => [ 49 | // 'cos:ListParts', 50 | // 'cos:PutObject', 51 | // 'cos:PostObject', 52 | // 'cos:InitiateMultipartUpload', 53 | // 'cos:UploadPart', 54 | // 'cos:CompleteMultipartUpload', 55 | // 'cos:AbortMultipartUpload', 56 | // 'cos:ListMultipartUploads', 57 | // ], 58 | // "resource" => [ 59 | // "qcs::cos:ap-beijing:uid/{uid}:{bucket}-{appid}/{date}/{uuid}/*", 60 | // ], 61 | // ] 62 | // ], 63 | // ], 64 | ], 65 | ]; 66 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/ 6 | vendor 7 | 8 | 9 | 10 | 11 | src/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | secretId, $this->secretKey); 27 | $httpProfile = new HttpProfile; 28 | $httpProfile->setEndpoint($this->endpoint ?: 'sts.tencentcloudapi.com'); 29 | 30 | $clientProfile = new ClientProfile; 31 | $clientProfile->setHttpProfile($httpProfile); 32 | $client = new StsClient($credential, $this->region ?: 'ap-guangzhou', $clientProfile); 33 | 34 | $request = new GetFederationTokenRequest; 35 | 36 | $request->fromJsonString( 37 | json_encode([ 38 | 'Name' => $name ?? config('app.name').'.'.uniqid(), 39 | 'Policy' => json_encode([ 40 | 'version' => '2.0', 41 | 'statement' => $statements, 42 | ]), 43 | 'DurationSeconds' => $this->expiresIn($expiresIn), 44 | ]) 45 | ); 46 | 47 | $response = $client->GetFederationToken($request); 48 | 49 | $credentials = new Credentials($response->getCredentials()->getToken(), $response->getCredentials()->getTmpSecretId(), $response->getCredentials()->getTmpSecretKey()); 50 | 51 | return tap(new Token( 52 | $credentials, 53 | $response->getExpiredTime(), 54 | $response->getExpiration(), 55 | $response->getRequestId(), 56 | $statements 57 | ), function ($token) { 58 | event(new TokenCreated($token)); 59 | }); 60 | } 61 | 62 | protected function expiresIn(int|string|Carbon $expiredAt): int 63 | { 64 | $format = fn ($expiresIn) => (int) (min($expiresIn, static::MAX_EXPIRES_IN)); 65 | 66 | return match (true) { 67 | is_int($expiredAt) => $format($expiredAt), 68 | is_object($expiredAt) => $format((int) $expiredAt->timestamp - time()), 69 | is_string($expiredAt) => $format((int) Carbon::parse($expiredAt)->timestamp - time()), 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Contracts/StrategyInterface.php: -------------------------------------------------------------------------------- 1 | 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array']])] 19 | public function getStatements(): array; 20 | 21 | public function getExpiresIn(): int; 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function getVariables(): array; 27 | 28 | public function createToken(): Token; 29 | } 30 | -------------------------------------------------------------------------------- /src/Credentials.php: -------------------------------------------------------------------------------- 1 | token; 25 | } 26 | 27 | public function getTmpSecretId(): string 28 | { 29 | return $this->tmpSecretId; 30 | } 31 | 32 | public function getTmpSecretKey(): string 33 | { 34 | return $this->tmpSecretKey; 35 | } 36 | 37 | #[ArrayShape(['token' => 'string', 'tmp_secret_id' => 'string', 'tmp_secret_key' => 'string'])] 38 | public function toArray(): array 39 | { 40 | return [ 41 | 'token' => $this->token, 42 | 'tmp_secret_id' => $this->tmpSecretId, 43 | 'tmp_secret_key' => $this->tmpSecretKey, 44 | ]; 45 | } 46 | 47 | public function toJson($options = 0): bool|string 48 | { 49 | return json_encode($this->toArray(), $options); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Events/TokenCreated.php: -------------------------------------------------------------------------------- 1 | config = new Repository($config); 29 | } 30 | 31 | /** 32 | * @throws InvalidArgumentException 33 | */ 34 | public function strategy(?string $strategy = null) 35 | { 36 | $strategy = $strategy ?: $this->getDefaultStrategy(); 37 | 38 | if (is_null($strategy)) { 39 | throw new InvalidArgumentException(sprintf( 40 | 'Unable to resolve NULL strategy for [%s].', 41 | static::class 42 | )); 43 | } 44 | 45 | if (! isset($this->strategies[$strategy])) { 46 | $this->strategies[$strategy] = $this->createStrategy($strategy); 47 | } 48 | 49 | return $this->strategies[$strategy]; 50 | } 51 | 52 | /** 53 | * @throws InvalidArgumentException 54 | */ 55 | protected function createStrategy($name): StrategyInterface 56 | { 57 | $strategyConfig = $this->getStrategyConfig($name); 58 | 59 | if (isset($this->customCreators[$name])) { 60 | return $this->callCustomCreator($name); 61 | } elseif (array_key_exists('strategies', $strategyConfig)) { 62 | return $this->createStackStrategy($name, $strategyConfig); 63 | } elseif (\array_key_exists($name, $this->config->get('strategies'))) { 64 | $method = 'create'.Str::studly($name).'Strategy'; 65 | 66 | if (method_exists($this, $method)) { 67 | return $this->$method(); 68 | } 69 | 70 | return new Strategy($name, $strategyConfig); 71 | } 72 | 73 | throw new InvalidArgumentException("Strategy [$name] not supported."); 74 | } 75 | 76 | /** 77 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException 78 | */ 79 | protected function createStackStrategy(string $strategyName, array $config): StrategyInterface 80 | { 81 | $strategies = []; 82 | 83 | foreach ($config['strategies'] ?? [] as $name) { 84 | $strategies[] = $this->strategy($name); 85 | } 86 | 87 | return new StackStrategy($strategies, $strategyName, $config); 88 | } 89 | 90 | protected function getStrategyConfig(string $strategyName): array 91 | { 92 | $defaultConfig = $this->config->get('default'); 93 | $strategyConfig = array_merge($defaultConfig, $this->config->get("strategies.$strategyName", [])); 94 | 95 | $strategyConfig['variables'] = array_merge( 96 | $this->config->get('default.variables', []), 97 | $this->config->get("strategies.{$strategyName}.variables", []) 98 | ); 99 | 100 | $defaultStatement = Arr::only($defaultConfig, ['action', 'effect', 'principal', 'resource', 'condition']); 101 | 102 | $strategyConfig['statements'] = array_map(fn ($statement) => array_merge($defaultStatement, $statement), $strategyConfig['statements'] ?? []); 103 | 104 | return $strategyConfig; 105 | } 106 | 107 | protected function callCustomCreator(string $strategy): StrategyInterface 108 | { 109 | return $this->customCreators[$strategy]($this->config->get("strategies.{$strategy}")); 110 | } 111 | 112 | public function extend($strategy, Closure $callback): static 113 | { 114 | $this->customCreators[$strategy] = $callback; 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @return array 121 | */ 122 | public function getStrategies(): array 123 | { 124 | return $this->strategies; 125 | } 126 | 127 | public function forgetStrategies(): static 128 | { 129 | $this->strategies = []; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * @throws InvalidArgumentException 136 | */ 137 | public function __call($method, $parameters) 138 | { 139 | return $this->strategy()->$method(...$parameters); 140 | } 141 | 142 | protected function getDefaultStrategy(): ?string 143 | { 144 | return array_key_first($this->config->get('strategies')); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/QcloudFederationTokenServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 15 | dirname(__DIR__).'/config/federation-token.php' => config_path('federation-token.php'), 16 | ], 'config'); 17 | 18 | $this->mergeConfigFrom(dirname(__DIR__).'/config/federation-token.php', 'federation-token'); 19 | } 20 | 21 | public function register() 22 | { 23 | $this->app->singleton(Manager::class, function () { 24 | return new Manager(config('federation-token')); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Statement.php: -------------------------------------------------------------------------------- 1 | setEffect($config['effect'] ?? 'allow'); 33 | 34 | if (! empty($config['principal'])) { 35 | $this->setPrincipal($config['principal']); 36 | } 37 | 38 | if (! empty($config['action'])) { 39 | $this->setAction($config['action']); 40 | } 41 | 42 | if (! empty($config['resource'])) { 43 | $this->setResource($config['resource']); 44 | } 45 | 46 | if (! empty($config['condition'])) { 47 | $this->setCondition($config['condition']); 48 | } 49 | } 50 | 51 | public function setVariables(array $variables): static 52 | { 53 | $this->variables = $variables; 54 | 55 | return $this; 56 | } 57 | 58 | public function setPrincipal(array $principal): static 59 | { 60 | $this->principal = $principal; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException 67 | */ 68 | public function setEffect(string $effect): static 69 | { 70 | if (! in_array($effect, ['allow', 'deny'])) { 71 | throw new InvalidArgumentException('Invalid effect value.'); 72 | } 73 | 74 | $this->effect = $effect; 75 | 76 | return $this; 77 | } 78 | 79 | public function allow(): static 80 | { 81 | $this->effect = 'allow'; 82 | 83 | return $this; 84 | } 85 | 86 | public function deny(): static 87 | { 88 | $this->effect = 'deny'; 89 | 90 | return $this; 91 | } 92 | 93 | public function setAction(array $action): static 94 | { 95 | $this->action = $action; 96 | 97 | return $this; 98 | } 99 | 100 | public function setResource(array $resource): static 101 | { 102 | $this->resource = $resource; 103 | 104 | return $this; 105 | } 106 | 107 | public function setCondition(array $condition): static 108 | { 109 | $this->condition = $condition; 110 | 111 | return $this; 112 | } 113 | 114 | public function __toString(): string 115 | { 116 | return json_encode($this->toArray()) ?? ''; 117 | } 118 | 119 | #[ArrayShape(['principal' => 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array'])] 120 | public function toArray(): array 121 | { 122 | $principal = $this->principal; 123 | 124 | if (! empty($principal['qcs'])) { 125 | $principal['qcs'] = array_map([$this, 'replaceVariables'], $principal['qcs']); 126 | } 127 | 128 | return [ 129 | 'principal' => $principal, 130 | 'effect' => $this->effect, 131 | 'action' => $this->action, 132 | 'resource' => array_map([$this, 'replaceVariables'], $this->resource), 133 | 'condition' => $this->condition, 134 | ]; 135 | } 136 | 137 | protected function replaceVariables(string $string): string 138 | { 139 | $variables = array_merge(...array_map(function ($key, $value) { 140 | return ['<'.$key.'>' => trim($value, '<>')]; 141 | }, array_keys($this->variables), $this->variables)); 142 | 143 | $replacements = array_merge([ 144 | '' => Str::uuid()->toString(), 145 | '' => \time(), 146 | '' => Str::random(16), 147 | '' => Str::random(32), 148 | '' => \date('Ymd'), 149 | '' => \date('Ymd'), 150 | '' => \date('YmdHis'), 151 | '' => \date('Y'), 152 | '' => \date('m'), 153 | '' => \date('d'), 154 | '' => \date('H'), 155 | '' => \date('i'), 156 | '' => \date('s'), 157 | ], $variables); 158 | 159 | return str_replace(array_keys($replacements), array_values($replacements), $string); 160 | } 161 | 162 | public function toJson($options = 0): bool|string 163 | { 164 | return json_encode($this->toArray()); 165 | } 166 | 167 | #[ArrayShape(['principal' => 'array', 'effect' => 'string', 'action' => 'array', 'resource' => 'array', 'condition' => 'array'])] 168 | public function jsonSerialize() 169 | { 170 | return $this->toArray(); 171 | } 172 | 173 | public function offsetExists(mixed $offset): bool 174 | { 175 | return property_exists($this, $offset); 176 | } 177 | 178 | public function offsetGet(mixed $offset) 179 | { 180 | return $this->toArray()[$offset] ?? null; 181 | } 182 | 183 | public function offsetSet(mixed $offset, mixed $value) 184 | { 185 | $method = sprintf('set%s', ucfirst($offset)); 186 | 187 | if (method_exists($this, $method)) { 188 | $this->$method($value); 189 | } 190 | } 191 | 192 | public function offsetUnset(mixed $offset) {} 193 | } 194 | -------------------------------------------------------------------------------- /src/Strategies/BuildTokens.php: -------------------------------------------------------------------------------- 1 | getBuilder()->build($this->getStatements(), $this->getExpiresIn(), $this->getName()); 16 | } 17 | 18 | /** 19 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 20 | */ 21 | public function getBuilder(): Builder 22 | { 23 | return new Builder($this->getSecretId(), $this->getSecretKey(), $this->getRegion(), $this->getEndpoint()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Strategies/StackStrategy.php: -------------------------------------------------------------------------------- 1 | config = new Repository($config); 20 | } 21 | 22 | public function getName() 23 | { 24 | return $this->config->get('name', $this->name); 25 | } 26 | 27 | /** 28 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 29 | */ 30 | public function getSecretId(): string 31 | { 32 | return $this->config->get('secret_id') ?? $this->getDefaultStrategy()->getSecretId(); 33 | } 34 | 35 | /** 36 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 37 | */ 38 | public function getRegion(): ?string 39 | { 40 | return $this->config->get('region') ?? $this->getDefaultStrategy()->getRegion(); 41 | } 42 | 43 | /** 44 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 45 | */ 46 | public function getEndpoint(): ?string 47 | { 48 | return $this->config->get('endpoint') ?? $this->getDefaultStrategy()->getEndpoint(); 49 | } 50 | 51 | /** 52 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 53 | */ 54 | public function getSecretKey(): string 55 | { 56 | return $this->config->get('secret_key') ?? $this->getDefaultStrategy()->getSecretKey(); 57 | } 58 | 59 | /** 60 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 61 | */ 62 | public function getExpiresIn(): int 63 | { 64 | return $this->config->get('expires_in', $this->config->get('duration_seconds', 1800)) ?? $this->getDefaultStrategy()->getExpiresIn(); 65 | } 66 | 67 | /** 68 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 69 | */ 70 | public function getVariables(): array 71 | { 72 | return $this->config->get('variables', []) ?? $this->getDefaultStrategy()->getVariables(); 73 | } 74 | 75 | /** 76 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 77 | */ 78 | public function getDefaultStrategy(): StrategyInterface 79 | { 80 | foreach ($this->strategies as $strategy) { 81 | if ($strategy instanceof StrategyInterface) { 82 | return $strategy; 83 | } 84 | } 85 | 86 | throw new InvalidConfigException('Invalid stack strategy config, no available strategy found.'); 87 | } 88 | 89 | #[ArrayShape([Statement::class])] 90 | public function getStatements(): array 91 | { 92 | $statements = []; 93 | 94 | foreach ($this->strategies as $strategy) { 95 | if ($strategy instanceof StrategyInterface) { 96 | $statements = array_merge($statements, $strategy->getStatements()); 97 | } 98 | } 99 | 100 | return $statements; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Strategies/Strategy.php: -------------------------------------------------------------------------------- 1 | config = new Repository($config); 25 | } 26 | 27 | public function getName() 28 | { 29 | return $this->config->get('name', $this->name); 30 | } 31 | 32 | public function getSecretId(): string 33 | { 34 | return $this->config->get('secret_id'); 35 | } 36 | 37 | public function getSecretKey(): string 38 | { 39 | return $this->config->get('secret_key'); 40 | } 41 | 42 | public function getEndpoint(): ?string 43 | { 44 | return $this->config->get('endpoint'); 45 | } 46 | 47 | public function getRegion(): ?string 48 | { 49 | return $this->config->get('region'); 50 | } 51 | 52 | public function getResources(): array 53 | { 54 | return $this->config->get('resource', []); 55 | } 56 | 57 | public function getExpiresIn(): int 58 | { 59 | return $this->config->get('expires_in', $this->config->get('duration_seconds', 1800)); 60 | } 61 | 62 | public function getVariables(): array 63 | { 64 | return $this->config->get('variables', []); 65 | } 66 | 67 | /** 68 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidConfigException 69 | * @throws \Overtrue\LaravelQcloudFederationToken\Exceptions\InvalidArgumentException 70 | */ 71 | #[ArrayShape([Statement::class])] 72 | public function getStatements(): array 73 | { 74 | $statements = $this->config->get('statements'); 75 | 76 | if (empty($statements)) { 77 | throw new InvalidConfigException('No statements found.'); 78 | } 79 | 80 | $variables = array_merge([ 81 | 'region' => $this->getRegion(), 82 | ], $this->getVariables()); 83 | 84 | return array_map(fn ($config) => (new Statement($config))->setVariables($variables)->toArray(), $statements); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Token.php: -------------------------------------------------------------------------------- 1 | credentials; 24 | } 25 | 26 | public function getExpiredAt(): int 27 | { 28 | return $this->expiredAt; 29 | } 30 | 31 | public function getExpiration(): string 32 | { 33 | return $this->expiration; 34 | } 35 | 36 | public function getRequestId(): string 37 | { 38 | return $this->requestId; 39 | } 40 | 41 | public function getStatements(): array 42 | { 43 | return $this->statements; 44 | } 45 | 46 | #[ArrayShape(['credentials' => 'mixed', 'expired_at' => 'int', 'expiration' => 'string'])] 47 | public function toArray(): array 48 | { 49 | return [ 50 | 'credentials' => $this->credentials->toArray(), 51 | 'expired_at' => $this->expiredAt, 52 | 'expiration' => $this->expiration, 53 | ]; 54 | } 55 | 56 | public function toJson($options = 0): bool|string 57 | { 58 | return json_encode($this->toArray(), $options); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/CredentialsTest.php: -------------------------------------------------------------------------------- 1 | assertSame('mock-token', $credentials->getToken()); 23 | $this->assertSame('mock-tmpSecretId', $credentials->getTmpSecretId()); 24 | $this->assertSame('mock-tmpSecretKey', $credentials->getTmpSecretKey()); 25 | } 26 | 27 | public function test_to_array() 28 | { 29 | $credentials = new Credentials( 30 | 'mock-token', 31 | 'mock-tmpSecretId', 32 | 'mock-tmpSecretKey' 33 | ); 34 | 35 | $this->assertInstanceOf(Arrayable::class, $credentials); 36 | 37 | $this->assertSame([ 38 | 'token' => 'mock-token', 39 | 'tmp_secret_id' => 'mock-tmpSecretId', 40 | 'tmp_secret_key' => 'mock-tmpSecretKey', 41 | ], $credentials->toArray()); 42 | } 43 | 44 | public function test_to_json() 45 | { 46 | $credentials = new Credentials( 47 | 'mock-token', 48 | 'mock-tmpSecretId', 49 | 'mock-tmpSecretKey' 50 | ); 51 | 52 | $this->assertInstanceOf(Jsonable::class, $credentials); 53 | 54 | $this->assertSame(json_encode([ 55 | 'token' => 'mock-token', 56 | 'tmp_secret_id' => 'mock-tmpSecretId', 57 | 'tmp_secret_key' => 'mock-tmpSecretKey', 58 | ]), $credentials->toJson()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/FeatureTest.php: -------------------------------------------------------------------------------- 1 | [ 23 | 'qcs' => [ 24 | 'qcs::cam::uid/1238423:uin/3232523', 25 | ], 26 | ], 27 | 'effect' => 'allow', 28 | 'action' => [ 29 | 'cos:PutObject', 30 | 'cos:GetObject', 31 | 'cos:HeadObject', 32 | 'cos:OptionsObject', 33 | 'cos:ListParts', 34 | 'cos:GetObjectTagging', 35 | ], 36 | 'resource' => [ 37 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*', 38 | 'qcs::cos:ap-guangzhou:uid/1238423:bucketB-1238423/object2', 39 | ], 40 | 'condition' => [ 41 | 'ip_equal' => [ 42 | 'qcs:ip' => '10.121.2.10/24', 43 | ], 44 | ], 45 | ]; 46 | 47 | config([ 48 | 'federation-token' => [ 49 | 'default' => [ 50 | 'secret_id' => 'default-secret-id', 51 | 'expired_at' => '+1 hour', 52 | ], 53 | 'strategies' => [ 54 | 'cvm' => [ 55 | 'secret_id' => 'secret-id', 56 | 'secret_key' => 'secret-key', 57 | 'region' => 'ap-tokyo', 58 | 'statements' => [$statement], 59 | ], 60 | 61 | 'cos' => [ 62 | 'secret_key' => 'secret-key', 63 | 'region' => 'ap-tokyo', 64 | 'statements' => [$statement], 65 | ], 66 | ], 67 | ], 68 | ]); 69 | 70 | $this->assertSame('secret-id', FederationToken::getSecretId()); 71 | $this->assertSame('default-secret-id', FederationToken::strategy('cos')->getSecretId()); 72 | 73 | $this->assertSame([$statement], FederationToken::getStatements()); 74 | } 75 | 76 | public function test_it_can_replace_vars() 77 | { 78 | $statement = [ 79 | 'principal' => [ 80 | 'qcs' => [ 81 | 'qcs::cam::uid/:uin/', 82 | ], 83 | ], 84 | 'effect' => 'allow', 85 | 'action' => [ 86 | 'cos:PutObject', 87 | 'cos:GetObject', 88 | 'cos:HeadObject', 89 | 'cos:OptionsObject', 90 | 'cos:ListParts', 91 | 'cos:GetObjectTagging', 92 | ], 93 | 'resource' => [ 94 | 'qcs::cos::uid/:bucketA-/*', 95 | 'qcs::cos::uid/:bucketA-///*', 96 | 'qcs::cos::uid/1238423:bucketB-/////object2', 97 | ], 98 | 'condition' => [ 99 | 'ip_equal' => [ 100 | 'qcs:ip' => '10.121.2.10/24', 101 | ], 102 | ], 103 | ]; 104 | 105 | config([ 106 | 'federation-token' => [ 107 | 'default' => [ 108 | 'secret_id' => 'default-secret-id', 109 | 'expired_at' => '+1 hour', 110 | 'variables' => [ 111 | 'uid' => 'mock-uid', 112 | 'uin' => 'mock-uin', 113 | 'appid' => 'mock-appid', 114 | 'region' => 'ap-guangzhou', 115 | ], 116 | ], 117 | 'strategies' => [ 118 | 'cos' => [ 119 | 'secret_key' => 'secret-key', 120 | 'region' => 'ap-tokyo', 121 | 'variables' => [ 122 | 'uid' => 'mock-uid-from-cos', 123 | 'uin' => 'mock-uin', 124 | 'appid' => 'mock-appid', 125 | 'var2' => 'mock-var2', 126 | ], 127 | 'statements' => [$statement], 128 | ], 129 | 'cvm' => [ 130 | 'secret_id' => 'secret-id', 131 | 'secret_key' => 'secret-key', 132 | 'region' => 'ap-tokyo', 133 | 'statements' => [$statement], 134 | ], 135 | ], 136 | ], 137 | ]); 138 | 139 | $statement = FederationToken::getStatements(); 140 | 141 | // "qcs::cam::uid/:uin/" 142 | $this->assertSame(['qcs' => ['qcs::cam::uid/mock-uid-from-cos:uin/mock-uin']], $statement[0]['principal']); 143 | 144 | // "qcs::cos:{region}:uid/:bucketA-/*", 145 | $this->assertSame( 146 | sprintf( 147 | 'qcs::cos:%s:uid/%s:bucketA-%s/*', 148 | 'ap-guangzhou', 149 | 'mock-uid-from-cos', 150 | 'mock-uid-from-cos', 151 | ), 152 | $statement[0]['resource'][0] 153 | ); 154 | 155 | // "qcs::cos::uid/:bucketA-///*", 156 | $this->assertSame( 157 | sprintf( 158 | 'qcs::cos:%s:uid/%s:bucketA-%s/%s/%s/*', 159 | 'ap-guangzhou', 160 | 'mock-uid-from-cos', 161 | 'mock-uid-from-cos', 162 | time(), 163 | 'mock-var2', 164 | ), 165 | $statement[0]['resource'][1] 166 | ); 167 | 168 | // "qcs::cos::uid/1238423:bucketB-/////object2" 169 | $this->assertSame( 170 | sprintf( 171 | 'qcs::cos:%s:uid/1238423:bucketB-%s/%s/%s/%s/%s/object2', 172 | 'ap-guangzhou', 173 | 'mock-appid', 174 | date('Ymd'), 175 | date('Y'), 176 | date('m'), 177 | date('d') 178 | ), 179 | $statement[0]['resource'][2] 180 | ); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /tests/StrategyTest.php: -------------------------------------------------------------------------------- 1 | 'secretId', 14 | 'secret_key' => 'secretKey', 15 | 'region' => 'ap-guangzhou', 16 | 'statements' => [ 17 | [ 18 | 'principal' => [ 19 | 'qcs' => [ 20 | 'qcs::cam::uid/1238423:uin/3232523', 21 | ], 22 | ], 23 | 'effect' => 'deny', 24 | 'action' => [ 25 | 'cos:PutObject', 26 | 'cos:GetObject', 27 | ], 28 | 'resource' => [ 29 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*', 30 | 'qcs::cos::uid/1238423:bucketB-1238423/object2', 31 | ], 32 | 'condition' => [ 33 | 'ip_equal' => [ 34 | 'qcs:ip' => '10.121.2.10/24', 35 | ], 36 | ], 37 | ], 38 | ], 39 | ]); 40 | 41 | $statement = $strategy->getStatements()[0]; 42 | 43 | $this->assertSame('secretId', $strategy->getSecretId()); 44 | $this->assertSame('secretKey', $strategy->getSecretKey()); 45 | $this->assertSame('ap-guangzhou', $strategy->getRegion()); 46 | $this->assertSame('deny', $statement['effect']); 47 | $this->assertSame([ 48 | 'cos:PutObject', 49 | 'cos:GetObject', 50 | ], $statement['action']); 51 | $this->assertSame([ 52 | 'qcs::cos:ap-beijing:uid/1238423:bucketA-1238423/*', 53 | 'qcs::cos:ap-guangzhou:uid/1238423:bucketB-1238423/object2', 54 | ], $statement['resource']); 55 | $this->assertSame([ 56 | 'ip_equal' => [ 57 | 'qcs:ip' => '10.121.2.10/24', 58 | ], 59 | ], $statement['condition']); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected function getPackageProviders($app): array 17 | { 18 | return [QcloudFederationTokenServiceProvider::class]; 19 | } 20 | 21 | /** 22 | * Define environment setup. 23 | * 24 | * @param Application $app 25 | */ 26 | protected function getEnvironmentSetUp($app) 27 | { 28 | // Setup default database to use sqlite :memory: 29 | $app['config']->set('database.default', 'testing'); 30 | $app['config']->set('database.connections.testing', [ 31 | 'driver' => 'sqlite', 32 | 'database' => ':memory:', 33 | 'prefix' => '', 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/TokenTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($credentials, $token->getCredentials()); 28 | $this->assertSame(1547696355, $token->getExpiredAt()); 29 | $this->assertSame('2022-05-05 05:05:05', $token->getExpiration()); 30 | $this->assertSame('mock-request-id', $token->getRequestId()); 31 | $this->assertSame(['mock-statement'], $token->getStatements()); 32 | } 33 | 34 | public function test_to_array() 35 | { 36 | $credentials = new Credentials( 37 | 'mock-token', 38 | 'mock-tmpSecretId', 39 | 'mock-tmpSecretKey' 40 | ); 41 | 42 | $token = new Token($credentials, 1547696355, '2022-05-05 05:05:05', 'mock-request-id', [ 43 | 'mock-statement', 44 | ]); 45 | 46 | $this->assertInstanceOf(Arrayable::class, $token); 47 | 48 | $this->assertSame([ 49 | 'credentials' => $credentials->toArray(), 50 | 'expired_at' => 1547696355, 51 | 'expiration' => '2022-05-05 05:05:05', 52 | ], $token->toArray()); 53 | } 54 | 55 | public function test_to_json() 56 | { 57 | $credentials = new Credentials( 58 | 'mock-token', 59 | 'mock-tmpSecretId', 60 | 'mock-tmpSecretKey' 61 | ); 62 | 63 | $token = new Token($credentials, 1547696355, '2022-05-05 05:05:05', 'mock-request-id'); 64 | 65 | $this->assertInstanceOf(Jsonable::class, $token); 66 | 67 | $this->assertSame(json_encode([ 68 | 'credentials' => $credentials->toArray(), 69 | 'expired_at' => 1547696355, 70 | 'expiration' => '2022-05-05 05:05:05', 71 | ]), $token->toJson()); 72 | } 73 | } 74 | --------------------------------------------------------------------------------