mirror of
http://124.126.16.154:8888/singularity/hyperf-saml.git
synced 2026-01-15 07:15:05 +08:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b5a398b5 | ||
|
|
aa1833d37c | ||
|
|
61dbf7f537 | ||
|
|
a541d7584a | ||
|
|
c0e298f778 | ||
|
|
d7929cbcf5 | ||
|
|
1356620e2d | ||
|
|
12905ccf63 | ||
|
|
de57588ef1 | ||
|
|
6012da8005 | ||
|
|
1013894cca | ||
|
|
03e717138d | ||
|
|
a53164a405 | ||
|
|
a00ff47c0c | ||
|
|
2c1d61e664 | ||
|
|
bd6f8af46f | ||
|
|
18d3bff2b7 | ||
|
|
537c296288 | ||
|
|
e742538c64 | ||
|
|
e2210e5a55 | ||
|
|
deb80b5b61 | ||
|
|
da35cf8442 | ||
|
|
045b361c5a | ||
|
|
9dde7b1187 | ||
|
|
2918b8e23a | ||
|
|
d994292d41 | ||
|
|
f4672c5152 | ||
|
|
37654602d9 | ||
|
|
9534e82708 | ||
|
|
ee2ca93349 | ||
|
|
1f2a339566 | ||
|
|
de71f7e565 | ||
|
|
9c8a5c7cbd | ||
|
|
39f6e7a8e1 | ||
|
|
de2d3f99c1 | ||
|
|
f356e84f78 | ||
|
|
0f1353c4f7 | ||
|
|
14231b1c5f | ||
|
|
f030bd0e5d |
28
.gitea/workflows/publish_on_tagged.yml
Normal file
28
.gitea/workflows/publish_on_tagged.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Release development version to registry
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '**.**'
|
||||
|
||||
jobs:
|
||||
Publish on Tagged:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
- name: List files in the repository
|
||||
run: |
|
||||
ls ${{ gitea.workspace }}
|
||||
- name: Zip files in the repository
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install zip
|
||||
zip -r dist.zip *
|
||||
- name: Publish to registry
|
||||
run: |
|
||||
curl --user ch4o5:4fd300672472e666014314c1c94c604c634165a9 \
|
||||
--upload-file ./dist.zip \
|
||||
https://nest.doylee.cn/api/packages/HDK/composer?version=${{ gitea.ref_name }}
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
/vendor/
|
||||
composer.lock
|
||||
*.cache
|
||||
*.log
|
||||
runtime/
|
||||
|
||||
# IDE support
|
||||
.idea/
|
||||
|
||||
@@ -1,89 +1,16 @@
|
||||
<?php
|
||||
|
||||
$header = <<<'EOF'
|
||||
This file is part of Hyperf.
|
||||
$finder = PhpCsFixer\Finder::create()->in([
|
||||
__DIR__ . '/publish',
|
||||
__DIR__ . '/src',
|
||||
__DIR__ . '/tests',
|
||||
]);
|
||||
|
||||
@link https://www.hyperf.io
|
||||
@document https://hyperf.wiki
|
||||
@contact group@hyperf.io
|
||||
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
EOF;
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
'@Symfony' => true,
|
||||
'@DoctrineAnnotation' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'header_comment' => [
|
||||
'comment_type' => 'PHPDoc',
|
||||
'header' => $header,
|
||||
'separate' => 'none',
|
||||
'location' => 'after_declare_strict',
|
||||
],
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short'
|
||||
],
|
||||
'list_syntax' => [
|
||||
'syntax' => 'short'
|
||||
],
|
||||
'concat_space' => [
|
||||
'spacing' => 'one'
|
||||
],
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => [
|
||||
'declare',
|
||||
],
|
||||
],
|
||||
'general_phpdoc_annotation_remove' => [
|
||||
'annotations' => [
|
||||
'author'
|
||||
],
|
||||
],
|
||||
'ordered_imports' => [
|
||||
'imports_order' => [
|
||||
'class', 'function', 'const',
|
||||
],
|
||||
'sort_algorithm' => 'alpha',
|
||||
],
|
||||
'single_line_comment_style' => [
|
||||
'comment_types' => [
|
||||
],
|
||||
],
|
||||
'yoda_style' => [
|
||||
'always_move_variable' => false,
|
||||
'equal' => false,
|
||||
'identical' => false,
|
||||
],
|
||||
'phpdoc_align' => [
|
||||
'align' => 'left',
|
||||
],
|
||||
'multiline_whitespace_before_semicolons' => [
|
||||
'strategy' => 'no_multi_line',
|
||||
],
|
||||
'constant_case' => [
|
||||
'case' => 'lower',
|
||||
],
|
||||
'class_attributes_separation' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'declare_strict_types' => true,
|
||||
'linebreak_after_opening_tag' => true,
|
||||
'lowercase_static_reference' => true,
|
||||
'no_useless_else' => true,
|
||||
'no_unused_imports' => true,
|
||||
'not_operator_with_successor_space' => true,
|
||||
'not_operator_with_space' => false,
|
||||
'ordered_class_elements' => true,
|
||||
'php_unit_strict' => false,
|
||||
'phpdoc_separation' => false,
|
||||
'single_quote' => true,
|
||||
'standardize_not_equals' => true,
|
||||
'multiline_comment_opening_closing' => true,
|
||||
])
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()
|
||||
->exclude('vendor')
|
||||
->in(__DIR__)
|
||||
)
|
||||
->setUsingCache(false);
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRules([
|
||||
'@PSR12' => true,
|
||||
'strict_param' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
])
|
||||
->setUsingCache(false)
|
||||
->setFinder($finder);
|
||||
|
||||
68
.versionrc
Normal file
68
.versionrc
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"header": "# 版本更新日志",
|
||||
"preMajor": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "feat",
|
||||
"section": "✨ Features | 新功能"
|
||||
},
|
||||
{
|
||||
"type": "fix",
|
||||
"section": "🐛 Bug Fixes | Bug 修复"
|
||||
},
|
||||
{
|
||||
"type": "init",
|
||||
"section": "🎉 Init | 初始化"
|
||||
},
|
||||
{
|
||||
"type": "docs",
|
||||
"section": "✏️ Documentation | 文档"
|
||||
},
|
||||
{
|
||||
"type": "style",
|
||||
"section": "💄 Styles | 风格"
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "♻️ Code Refactoring | 代码重构"
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "⚡ Performance Improvements | 性能优化"
|
||||
},
|
||||
{
|
||||
"type": "tests",
|
||||
"section": "✅ Tests | 测试"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"section": "✅ Tests | 测试"
|
||||
},
|
||||
{
|
||||
"type": "revert",
|
||||
"section": "⏪ Revert | 回退"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"section": "📦 Build System | 打包构建"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"section": "🚀 Chore | 构建/工程依赖/工具"
|
||||
},
|
||||
{
|
||||
"type": "ci",
|
||||
"section": "👷 Continuous Integration | CI 配置"
|
||||
}
|
||||
],
|
||||
"bumpFiles": [
|
||||
{
|
||||
"filename": "VERSION_TRACKER.txt",
|
||||
"type": "plain-text"
|
||||
},
|
||||
{
|
||||
"filename": "composer.json",
|
||||
"type": "json"
|
||||
}
|
||||
]
|
||||
}
|
||||
119
CHANGELOG.md
119
CHANGELOG.md
@@ -1,4 +1,123 @@
|
||||
# 版本更新日志
|
||||
## [1.0.0-beta.3](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2025-04-10)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes | Bug 修复
|
||||
|
||||
* **authn:** 修复不再被支持的 swoole 变量 ([aa1833d](http://124.126.16.154:8888/singularity/hyperf-saml/commit/aa1833d37c1052d322bfbb6a279ed9f6dfd6f743))
|
||||
|
||||
## [1.0.0-beta.2](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2024-05-21)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 更新依赖 ([c0e298f](http://124.126.16.154:8888/singularity/hyperf-saml/commit/c0e298f7785cf58724fede60ccb4d1b04e096e85))
|
||||
|
||||
|
||||
### ✨ Features | 新功能
|
||||
|
||||
* **sp:** 实现了定制 entityId 等参数的功能 ([a541d75](http://124.126.16.154:8888/singularity/hyperf-saml/commit/a541d7584ae874529c0b8f06f3490d85225e4d8e))
|
||||
|
||||
## [1.0.0-beta.1](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-beta.0...v1.0.0-beta.1) (2023-12-27)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes | Bug 修复
|
||||
|
||||
* **sp.sso:** 修复了参数不全可能被当成 assertion 误处理的问题 ([1356620](http://124.126.16.154:8888/singularity/hyperf-saml/commit/1356620e2d9a9180a93c2ef0c7c819d097ee121f))
|
||||
|
||||
## [1.0.0-beta.0](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-alpha.4...v1.0.0-beta.0) (2023-12-26)
|
||||
|
||||
|
||||
### 💄 Styles | 风格
|
||||
|
||||
* formatter ([6012da8](http://124.126.16.154:8888/singularity/hyperf-saml/commit/6012da8005ff7e9b14b5e6b44e54f8711ff0e03c))
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **release:** 更新到 beta字段 ([de57588](http://124.126.16.154:8888/singularity/hyperf-saml/commit/de57588ef1628b72e006e2bfe7401d5332e95153))
|
||||
* 增加lint/prettier/unitTest ([1013894](http://124.126.16.154:8888/singularity/hyperf-saml/commit/1013894ccafcfde578475decfdb59b91c81a727d))
|
||||
|
||||
## [1.0.0-alpha.4](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2023-12-04)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 迁移到 hyperf3.1 ([a53164a](http://124.126.16.154:8888/singularity/hyperf-saml/commit/a53164a4059abdc21bf38c94ce6ba36a112ff118))
|
||||
|
||||
## [1.0.0-alpha.3](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2023-10-30)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 更新依赖 ([2c1d61e](http://124.126.16.154:8888/singularity/hyperf-saml/commit/2c1d61e664a4c09b3c16ae2374552f7ccc7976be))
|
||||
|
||||
## [1.0.0-alpha.2](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2023-10-23)
|
||||
|
||||
|
||||
### 👷 Continuous Integration | CI 配置
|
||||
|
||||
* 修复内容为空的问题 ([18d3bff](http://124.126.16.154:8888/singularity/hyperf-saml/commit/18d3bff2b7b2daece8295437dde058a8c62f9a92))
|
||||
|
||||
## [1.0.0-alpha.1](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v1.0.0-alpha.0...v1.0.0-alpha.1) (2023-10-23)
|
||||
|
||||
|
||||
### 👷 Continuous Integration | CI 配置
|
||||
|
||||
* **gitea:** 引入 actions ([e2210e5](http://124.126.16.154:8888/singularity/hyperf-saml/commit/e2210e5a557f8e8e17160077f64b6ebb42127bdf))
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* 引入 gitea 仓库 ([e742538](http://124.126.16.154:8888/singularity/hyperf-saml/commit/e742538c6499438e1f149ade25fb99549f4beb79))
|
||||
* **composer:** 更新依赖 ([deb80b5](http://124.126.16.154:8888/singularity/hyperf-saml/commit/deb80b5b6115c1c4caa6e6c1e9594057f310eb4a))
|
||||
* **release:** 增加预发布版本的后缀 ([da35cf8](http://124.126.16.154:8888/singularity/hyperf-saml/commit/da35cf84421fefb133db11e21c6840fcd4c17a70))
|
||||
|
||||
## [1.0.0-alpha.0](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v0.2.3...v1.0.0-alpha.0) (2023-07-25)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 更新依赖到 hdk 1.0 ([9dde7b1](http://124.126.16.154:8888/singularity/hyperf-saml/commit/9dde7b118761d935007d161f21c99ffa3827d281))
|
||||
|
||||
### [0.2.3](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v0.2.2...v0.2.3) (2023-03-21)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 迁移到腾讯源,并更新依赖 ([d994292](http://124.126.16.154:8888/singularity/hyperf-saml/commit/d994292d41aab83a1e295e6b1fd3c96a075589d3))
|
||||
|
||||
### [0.2.2](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v0.2.1...v0.2.2) (2023-03-21)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 更新依赖 ([3765460](http://124.126.16.154:8888/singularity/hyperf-saml/commit/37654602d935518473a7c275afc54fd39c9e9ef1))
|
||||
|
||||
### [0.2.1](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v0.2.0...v0.2.1) (2023-03-15)
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 将依赖的 hdk 改为了稳定版 ([ee2ca93](http://124.126.16.154:8888/singularity/hyperf-saml/commit/ee2ca93349d86fc7b0edf0c94ffdc68d5ab90710))
|
||||
|
||||
## [0.2.0](http://124.126.16.154:8888/singularity/hyperf-saml/compare/v0.1.1...v0.2.0) (2023-03-15)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes | Bug 修复
|
||||
|
||||
* 尝试修复无法登录的问题 ([9c8a5c7](http://124.126.16.154:8888/singularity/hyperf-saml/commit/9c8a5c7cbdfa07a0020191ad53511b3523e75854))
|
||||
|
||||
|
||||
### 📦 Build System | 打包构建
|
||||
|
||||
* **composer:** 更新依赖 ([39f6e7a](http://124.126.16.154:8888/singularity/hyperf-saml/commit/39f6e7a8e145a2bcd6b0d6b234e95efe5e7a97ef))
|
||||
* **composer:** 设置hdk版本 ([f030bd0](http://124.126.16.154:8888/singularity/hyperf-saml/commit/f030bd0e5d2678508d8f3b3af7391d81103f7227))
|
||||
* **git:** 将 composer.lock 添加回版本控制 ([14231b1](http://124.126.16.154:8888/singularity/hyperf-saml/commit/14231b1c5faca15aab8813f1f0fdd2a6cbc93599))
|
||||
* **migrate:** 迁移到 hdk-auth ([de2d3f9](http://124.126.16.154:8888/singularity/hyperf-saml/commit/de2d3f99c15359599ce1d1913d72107a0d23c0bc))
|
||||
* **semver:** 添加了发版脚本和配置文件 ([de71f7e](http://124.126.16.154:8888/singularity/hyperf-saml/commit/de71f7e5653a66a3be01aa4286d4617c2e51eab1))
|
||||
* 更新版本到 hyperf3 ([f356e84](http://124.126.16.154:8888/singularity/hyperf-saml/commit/f356e84f78f5502ef09a5c8b539c107a7b2d5ebd))
|
||||
|
||||
### 0.1.1 (2023-02-03)
|
||||
|
||||
|
||||
|
||||
@@ -18,40 +18,52 @@
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "~8.0",
|
||||
"php": ">=8.1",
|
||||
"ext-redis": "*",
|
||||
"hyperf/config": "^2.2",
|
||||
"hyperf/constants": "^2.2",
|
||||
"hyperf/di": "2.2.*",
|
||||
"hyperf/http-server": "2.2.*",
|
||||
"hyperf/translation": "^2.2",
|
||||
"hyperf/validation": "^2.2",
|
||||
"hyperf/config": "3.1.*",
|
||||
"hyperf/constants": "3.1.*",
|
||||
"hyperf/di": "3.1.*",
|
||||
"hyperf/http-server": "3.1.*",
|
||||
"hyperf/translation": "3.1.*",
|
||||
"hyperf/validation": "3.1.*",
|
||||
"litesaml/lightsaml": "~3.0.0",
|
||||
"singularity/hdk-core": "0.1.x-dev",
|
||||
"singularity/hyperf-development-kit": "dev-develop",
|
||||
"singularity/hdk-core": "^1.0.0",
|
||||
"singularity/hdk-auth": "^1.0.0",
|
||||
"teapot/status-code": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"friendsofhyperf/pest-plugin-hyperf": "3.1.*",
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpunit/phpunit": ">=7.0",
|
||||
"swoole/ide-helper": "^4.5"
|
||||
},
|
||||
"suggest": {
|
||||
"swow/swow": "Required to create swow components."
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"minimum-stability": "alpha",
|
||||
"prefer-stable": true,
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true,
|
||||
"secure-http": false
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit -c phpunit.xml --colors=always",
|
||||
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
|
||||
"cs-fix": "php-cs-fixer fix $1"
|
||||
"test": [
|
||||
"rm -rf runtime",
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"pest --coroutine --prepend tests/bootstrap.php --colors=always"
|
||||
],
|
||||
"cs-fix": "php-cs-fixer fix $1 --rules=@PSR12 --allow-risky=yes",
|
||||
"analyse": "phpstan analyse $1",
|
||||
"ci": [
|
||||
"@analyse publish/ src/ tests/",
|
||||
"@cs-fix",
|
||||
"@test",
|
||||
"echo CI Success"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"hyperf": {
|
||||
@@ -62,22 +74,14 @@
|
||||
"bin/generate-cert"
|
||||
],
|
||||
"repositories": {
|
||||
"lux-map": {
|
||||
"nest": {
|
||||
"type": "composer",
|
||||
"url": "https://satis.luxcreo.cn/"
|
||||
"url": "https://nest.doylee.cn/api/packages/HDK/composer"
|
||||
},
|
||||
"packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.aliyun.com/composer/"
|
||||
},
|
||||
"packagist-tx": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.cloud.tencent.com/composer/"
|
||||
},
|
||||
"packagist-hw": {
|
||||
"type": "composer",
|
||||
"url": "https://repo.huaweicloud.com/repository/php/"
|
||||
}
|
||||
},
|
||||
"version": "0.1.1"
|
||||
"version": "1.0.0-beta.3"
|
||||
}
|
||||
|
||||
12118
composer.lock
generated
Normal file
12118
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
phpstan.dist.neon
Normal file
15
phpstan.dist.neon
Normal file
@@ -0,0 +1,15 @@
|
||||
parameters:
|
||||
level: 1
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
paths:
|
||||
- publish
|
||||
- src
|
||||
- tests
|
||||
ignoreErrors:
|
||||
- '#Constant BASE_PATH not found#'
|
||||
- '#Unknown parameter \$[a-zA-Z0-9]+ in call to callable Closure\.#'
|
||||
- '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read\.#'
|
||||
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9]+\(\) is unused\.#'
|
||||
- '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9]+\(\) has parameter \$response with no value type specified in iterable type array\.#'
|
||||
- '#Undefined variable: \$this#'
|
||||
33
phpunit.xml
33
phpunit.xml
@@ -1,15 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
verbose="true"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false">
|
||||
<testsuite name="Testsuite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</phpunit>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
stopOnFailure="true"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Testsuite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>./src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
||||
@@ -14,4 +14,4 @@ return [
|
||||
'saml_request' => 'SAMLRequest is required',
|
||||
'saml_response' => 'SAMLResponse is required',
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
@@ -14,4 +14,4 @@ return [
|
||||
'saml_request' => 'SAMLRequest 参数不能为空',
|
||||
'saml_response' => 'SAMLResponse 参数不能为空',
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Hyperf\Support\env;
|
||||
|
||||
return [
|
||||
// 当前项目类型
|
||||
'type' => 'sp', // 可选值:sp/idp
|
||||
|
||||
|
||||
// 是否支持多用户同时在线
|
||||
'allow_multi_online' => true,
|
||||
|
||||
|
||||
// IDP 相关配置
|
||||
'server' => [
|
||||
// common config
|
||||
'idp_id' => env('IDP_ID', 'https://test-accountx.luxcreo.cn/api/v1/auth'),
|
||||
// 单点登录
|
||||
|
||||
|
||||
// idp config
|
||||
|
||||
|
||||
// sp config
|
||||
// 以下内容向服务端申请
|
||||
'idp_assertion_url' => env('IDP_ASSERTION_URL', 'https://test-accountx.luxcreo.cn/api/v1/auth/assertion'),
|
||||
@@ -24,16 +26,16 @@ return [
|
||||
'idp_logout_url' => env('IDP_LOGOUT_URL', 'https://test-accountx.luxcreo.cn/api/v1/slo'),
|
||||
//单点退出
|
||||
],
|
||||
|
||||
|
||||
// SP 相关配置
|
||||
'client' => [
|
||||
// sp config
|
||||
// 以下内容向服务端申请
|
||||
'entity_id' => env('ENTITY_ID', ''), // TODO 业务系统唯一标识
|
||||
'acs_url' => env('ACS_URL', ''), // TODO 回调地址
|
||||
'landing_host' =>env('LANDING_HOST', ''), // TODO 站点 host
|
||||
'landing_host' => env('LANDING_HOST', ''), // TODO 站点 host
|
||||
],
|
||||
|
||||
|
||||
// 证书
|
||||
'credential' => [
|
||||
'enable' => false,
|
||||
@@ -42,4 +44,4 @@ return [
|
||||
'crt' => 'saml.crt',
|
||||
'pem' => 'saml.pem',
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
@@ -6,4 +6,4 @@ docker run \
|
||||
-w "/srv/www" \
|
||||
-v "$(pwd)":/srv/www \
|
||||
-v ~/.ssh:/root/.ssh \
|
||||
harbor.luxcreo.cn/library/hyperf:8.0-swoole /bin/ash
|
||||
harbor.luxcreo.cn/library/hyperf:8.1-swoole /bin/ash
|
||||
|
||||
5
scripts/release.sh
Executable file
5
scripts/release.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
docker run --rm -it \
|
||||
-v $(pwd):/app -e "GIT_AUTHOR_NAME=ch4o5" -e "EMAIL=dongyun.li@luxcreo.ai" \
|
||||
detouched/standard-version:latest -p=beta $1
|
||||
@@ -7,6 +7,7 @@
|
||||
* Created on 2022/4/25
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Constants;
|
||||
|
||||
use Hyperf\Constants\AbstractConstants;
|
||||
@@ -23,27 +24,26 @@ use Hyperf\Constants\Annotation\Constants;
|
||||
*/
|
||||
class SamlErrorCode extends AbstractConstants
|
||||
{
|
||||
|
||||
// 203 SAML 鉴权
|
||||
/**
|
||||
* @Message("saml_error.default")
|
||||
*/
|
||||
public const AUTH_SAML_ERROR = 203000;
|
||||
|
||||
|
||||
// 20301 验证
|
||||
/**
|
||||
* @Message("saml_error.params.default")
|
||||
*/
|
||||
public const AUTH_SAML_REQUEST_PARAMS_ERROR = 2030100;
|
||||
|
||||
|
||||
/**
|
||||
* @Message("saml_error.params.saml_request")
|
||||
*/
|
||||
public const AUTH_SAML_REQUEST_PARAMS_SAML_REQUEST = 2030101;
|
||||
|
||||
|
||||
/**
|
||||
* @Message("saml_error.params.saml_response")
|
||||
*/
|
||||
public const AUTH_SAML_REQUEST_PARAMS_SAML_RESPONSE = 2030102;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
||||
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
@@ -16,6 +17,8 @@ use Singularity\HyperfSaml\Services\Idp\AbstractLoginService;
|
||||
use Singularity\HyperfSaml\Services\Idp\AbstractLogoutService;
|
||||
use Throwable;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* IDP 相关错误捕获
|
||||
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
||||
@@ -31,31 +34,31 @@ class SamlIdpHandler extends ExceptionHandler
|
||||
* @var \Singularity\HyperfSaml\Services\Base
|
||||
*/
|
||||
private Base $base;
|
||||
|
||||
|
||||
/**
|
||||
* @Inject
|
||||
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLoginService
|
||||
*/
|
||||
private AbstractLoginService $loginService;
|
||||
|
||||
|
||||
/**
|
||||
* @Inject
|
||||
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLogoutService
|
||||
*/
|
||||
private AbstractLogoutService $logoutService;
|
||||
|
||||
|
||||
/**
|
||||
* @Inject
|
||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
||||
*/
|
||||
private StdoutLogger $logger;
|
||||
|
||||
|
||||
/**
|
||||
* @Inject(required=false)
|
||||
* @var \Hyperf\HttpServer\Contract\RequestInterface|null
|
||||
*/
|
||||
private ?RequestInterface $request;
|
||||
|
||||
|
||||
/**
|
||||
* @param RuntimeException $throwable
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
@@ -67,7 +70,7 @@ class SamlIdpHandler extends ExceptionHandler
|
||||
{
|
||||
// 阻止异常冒泡
|
||||
$this->stopPropagation();
|
||||
|
||||
|
||||
// IDP 相关
|
||||
if ($throwable instanceof ValidationException) {
|
||||
$SAMLResponse = $this->logoutService->createLogoutResponse(
|
||||
@@ -84,7 +87,7 @@ class SamlIdpHandler extends ExceptionHandler
|
||||
messageId: $throwable->getMessageId(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$error_type = get_class($throwable);
|
||||
$data = [
|
||||
'errorCode' => $throwable->getCode(),
|
||||
@@ -98,7 +101,7 @@ class SamlIdpHandler extends ExceptionHandler
|
||||
'sp' => config('saml.client.entity_id'),
|
||||
'messageId' => $throwable->getMessageId(),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
|
||||
$this->logger->error(
|
||||
<<<ERROR_LOG
|
||||
TYPE: $error_type
|
||||
@@ -136,13 +139,13 @@ ERROR_LOG
|
||||
$throwable->getRelayState(),
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
$this->logger->error("location: $url");
|
||||
return $response
|
||||
->withStatus(302)
|
||||
->withAddedHeader('Location', $url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -150,4 +153,4 @@ ERROR_LOG
|
||||
{
|
||||
return $throwable instanceof RuntimeException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
||||
|
||||
use Exception;
|
||||
use Hyperf\Codec\Json;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||
use Hyperf\Framework\Logger\StdoutLogger;
|
||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\Utils\Codec\Json;
|
||||
use Lmc\HttpConstants\Header;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Singularity\HyperfSaml\Constants\SamlErrorCode;
|
||||
@@ -17,6 +19,8 @@ use Singularity\HyperfSaml\Exceptions\ValidationException;
|
||||
use Teapot\StatusCode\RFC\RFC7231;
|
||||
use Throwable;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* SP 相关错误捕获
|
||||
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
||||
@@ -29,45 +33,45 @@ class SamlSpHandler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* @Inject
|
||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
||||
* @var StdoutLogger
|
||||
*/
|
||||
private StdoutLogger $logger;
|
||||
|
||||
|
||||
/**
|
||||
* @Inject(required=false)
|
||||
* @var \Hyperf\HttpServer\Contract\RequestInterface|null
|
||||
* @var RequestInterface|null
|
||||
*/
|
||||
private ?RequestInterface $request;
|
||||
|
||||
|
||||
/**
|
||||
* @param RuntimeException $throwable
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* @param RuntimeException $throwable
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws \Exception
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
// 阻止异常冒泡
|
||||
$this->stopPropagation();
|
||||
|
||||
|
||||
$restful = config('common.response.restful');
|
||||
$code_name = config('common.response.code_name');
|
||||
$message_name = config('common.response.message_name');
|
||||
|
||||
|
||||
$this->request?->url();
|
||||
|
||||
|
||||
$error_type = get_class($throwable);
|
||||
|
||||
|
||||
$is_testing = config('app_status') === true;
|
||||
$is_debug = $this->request?->hasHeader('Postman-Token')
|
||||
|| $this->request?->header('User-Agent') === 'apifox/2.1.8 (https://www.apifox.cn)';
|
||||
|
||||
|
||||
$data = [
|
||||
$code_name => $throwable->getCode(),
|
||||
$message_name => $throwable->getMessage(),
|
||||
];
|
||||
|
||||
|
||||
// 验证失败
|
||||
if ($throwable instanceof ValidationException) {
|
||||
$code = $throwable->getCode();
|
||||
@@ -82,8 +86,8 @@ class SamlSpHandler extends ExceptionHandler
|
||||
$data['availableValue'] = $throwable->getAvailableValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 整理日志所需的数据
|
||||
$request_time = date('Y-m-d H:i:s');
|
||||
$request_headers = $this->request->getHeaders();
|
||||
@@ -93,20 +97,19 @@ class SamlSpHandler extends ExceptionHandler
|
||||
'sp' => config('saml.client.entity_id'),
|
||||
'messageId' => $throwable->getMessageId(),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
|
||||
|
||||
|
||||
$response = $response->withHeader(
|
||||
Header::CONTENT_TYPE,
|
||||
'application/json; charset=utf-8'
|
||||
);
|
||||
|
||||
|
||||
if ($is_debug && $is_testing) {
|
||||
$data['trace'] = [
|
||||
'errorType' => $error_type,
|
||||
'errorTrack' => $throwable->getTrace(),
|
||||
];
|
||||
}
|
||||
$cookies = json_encode($this->request->getCookieParams(), JSON_UNESCAPED_UNICODE);
|
||||
$this->logger->error(
|
||||
<<<ERROR_LOG
|
||||
TYPE: $error_type
|
||||
@@ -139,11 +142,10 @@ TRACE:
|
||||
|
||||
ERROR_LOG
|
||||
);
|
||||
|
||||
|
||||
$data = Json::encode($data);
|
||||
if ($restful) {
|
||||
$response = $response->withStatus(
|
||||
$status_code ??
|
||||
$throwable->status ??
|
||||
$throwable->statusCode ??
|
||||
RFC7231::INTERNAL_SERVER_ERROR
|
||||
@@ -154,7 +156,7 @@ ERROR_LOG
|
||||
new SwooleStream($data)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -162,4 +164,4 @@ ERROR_LOG
|
||||
{
|
||||
return $throwable instanceof RuntimeException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Exceptions\Logout;
|
||||
|
||||
use Singularity\HyperfSaml\Exceptions\ValidationException as SAMLValidationException;
|
||||
|
||||
class ValidationException extends SAMLValidationException
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
@@ -17,7 +18,7 @@ class RuntimeException extends \RuntimeException
|
||||
) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -25,7 +26,7 @@ class RuntimeException extends \RuntimeException
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -33,7 +34,7 @@ class RuntimeException extends \RuntimeException
|
||||
{
|
||||
return $this->relayState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -41,4 +42,4 @@ class RuntimeException extends \RuntimeException
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Exceptions;
|
||||
|
||||
// use App\Model\ServiceProvider;
|
||||
@@ -29,17 +30,17 @@ class ValidationException extends RuntimeException
|
||||
SamlConstants::STATUS_INVALID_NAME_ID_POLICY,
|
||||
$messageId,
|
||||
$relayState,
|
||||
$message ?? SamlErrorCode::getMessage($code) ,
|
||||
$message ?? SamlErrorCode::getMessage($code),
|
||||
$code,
|
||||
$previous
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function getFieldName(): string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -47,7 +48,7 @@ class ValidationException extends RuntimeException
|
||||
{
|
||||
return $this->currentValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -55,4 +56,4 @@ class ValidationException extends RuntimeException
|
||||
{
|
||||
return $this->availableValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,11 @@ abstract class AbstractService
|
||||
* @Value("idp_id")
|
||||
*/
|
||||
protected ?string $idpId;
|
||||
|
||||
/**
|
||||
* @Inject
|
||||
* @var \Hyperf\HttpServer\Contract\RequestInterface
|
||||
*/
|
||||
protected RequestInterface $request;
|
||||
|
||||
|
||||
public function __construct(protected RequestInterface $request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 SAMLRequest
|
||||
*
|
||||
@@ -54,14 +52,14 @@ abstract class AbstractService
|
||||
try {
|
||||
$bindingFactory = new BindingFactory();
|
||||
$binding = $bindingFactory->getBindingByRequest($requestInstance);
|
||||
|
||||
|
||||
// We prepare a message context to receive our SAML Request message.
|
||||
$messageContext = new MessageContext();
|
||||
|
||||
|
||||
// The received method fills in the messageContext with the SAML Request data.
|
||||
/** @var \LightSaml\Model\Protocol\Response $response */
|
||||
$binding->receive($requestInstance, $messageContext);
|
||||
|
||||
|
||||
return $messageContext;
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
throw new ValidationException(
|
||||
@@ -70,7 +68,7 @@ abstract class AbstractService
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 响应给 ACS
|
||||
*
|
||||
@@ -95,10 +93,10 @@ abstract class AbstractService
|
||||
}
|
||||
$messageContext = new MessageContext();
|
||||
$messageContext->setMessage($responseInstance);
|
||||
|
||||
|
||||
$bindingFactory = new BindingFactory();
|
||||
$redirectBinding = $bindingFactory->create($bindingType);
|
||||
|
||||
|
||||
// Ensure we include the RelayState.
|
||||
$message = $messageContext->getMessage();
|
||||
$rs = $message->getRelayState();
|
||||
@@ -106,29 +104,29 @@ abstract class AbstractService
|
||||
$message->setRelayState($relayState);
|
||||
$messageContext->setMessage($message);
|
||||
}
|
||||
|
||||
|
||||
// Return the Response.
|
||||
/** @var \Symfony\Component\HttpFoundation\RedirectResponse $httpResponse */
|
||||
$httpResponse = $redirectBinding->send($messageContext);
|
||||
|
||||
|
||||
$url_parts = parse_url($httpResponse->getTargetUrl());
|
||||
|
||||
|
||||
$query = [];
|
||||
parse_str($url_parts['query'], $query);
|
||||
// $query['Signature'] = $this->base64Wrapper->encode($query['Signature']);
|
||||
|
||||
|
||||
$query += $extraParams;
|
||||
$url_parts['query'] = http_build_query($query);
|
||||
$url_parts['port'] ??= '80';
|
||||
if ($url_parts['port'] !== '80') {
|
||||
$url_parts['host'] .= ':' . $url_parts['port'];
|
||||
}
|
||||
|
||||
|
||||
$url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];
|
||||
if (!$returnHtml) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
return sprintf(
|
||||
'<!DOCTYPE html>
|
||||
<html lang="">
|
||||
@@ -144,7 +142,7 @@ abstract class AbstractService
|
||||
);
|
||||
// return $httpResponse->setTargetUrl($url)->getContent();
|
||||
}
|
||||
|
||||
|
||||
public function requestParser($request): Request
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
@@ -154,7 +152,7 @@ abstract class AbstractService
|
||||
$params,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the certificate from the IdP.
|
||||
*
|
||||
@@ -164,7 +162,7 @@ abstract class AbstractService
|
||||
{
|
||||
return X509Certificate::fromFile('cert/server.crt');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the private key from the Idp.
|
||||
*
|
||||
@@ -174,4 +172,4 @@ abstract class AbstractService
|
||||
{
|
||||
return KeyHelper::createPrivateKey('cert/server.key', '', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,12 @@ use LightSaml\Model\Protocol\LogoutResponse;
|
||||
use LightSaml\Model\Protocol\Response;
|
||||
use LightSaml\Model\XmlDSig\SignatureStringReader;
|
||||
use LightSaml\SamlConstants;
|
||||
use Singularity\HDK\Account\Resource\User;
|
||||
use RobRichards\XMLSecLibs\XMLSecurityKey;
|
||||
use Singularity\HDK\Auth\Resource\User;
|
||||
use Throwable;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* Singularity\HyperfSaml\Lib\Base@HyperfSaml
|
||||
*
|
||||
@@ -52,8 +55,8 @@ class Base extends AbstractService
|
||||
* @param string $idpID
|
||||
* @param string $issuer
|
||||
* @param string $relayState
|
||||
* @param bool $returnUrl
|
||||
* @param array $exactArguments
|
||||
* @param bool $returnUrl
|
||||
* @param array $exactArguments
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
@@ -68,7 +71,7 @@ class Base extends AbstractService
|
||||
// $certificate = X509Certificate::fromFile('../cert/saml.crt');
|
||||
// $privateKey = KeyHelper::createPrivateKey('../cert/saml.pem', '', true);
|
||||
$context = new SerializationContext();
|
||||
|
||||
|
||||
$authnRequest = new AuthnRequest();
|
||||
$authnRequest
|
||||
->setAssertionConsumerServiceURL($acsUrl)
|
||||
@@ -78,19 +81,19 @@ class Base extends AbstractService
|
||||
->setIssuer(new Issuer($issuer))
|
||||
// ->setSignature(SignatureWriter::createByKeyAndCertificate($certificate, $privateKey))
|
||||
->serialize($context->getDocument(), $context);
|
||||
|
||||
|
||||
return $this->buildUrl($authnRequest, $relayState, $exactArguments, $returnUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成 URL
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\AuthnRequest|\LightSaml\Model\Protocol\LogoutRequest|string $request 也可以直接传入
|
||||
* @param AuthnRequest|LogoutRequest|string $request 也可以直接传入
|
||||
* base_url,带不带
|
||||
* query 都支持
|
||||
* @param string $relayState
|
||||
* @param array $exactArguments
|
||||
* @param bool $returnUrl
|
||||
* @param string $relayState
|
||||
* @param array $exactArguments
|
||||
* @param bool $returnUrl
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
@@ -108,21 +111,21 @@ class Base extends AbstractService
|
||||
$httpResponse = $redirectBinding->send($messageContext);
|
||||
$request = $httpResponse->getTargetUrl();
|
||||
}
|
||||
|
||||
|
||||
$url_parts = parse_url($request);
|
||||
|
||||
|
||||
$query = [];
|
||||
parse_str($url_parts['query'], $query);
|
||||
if (!empty($relayState)) {
|
||||
$query['RelayState'] = $relayState;
|
||||
}
|
||||
// $query['Signature'] = Base64Wrapper::encode($query['Signature']);
|
||||
|
||||
|
||||
$query += $exactArguments;
|
||||
$url_parts['query'] = http_build_query($query);
|
||||
|
||||
|
||||
$port = isset($url_parts['port']) ? ":{$url_parts['port']}" : '';
|
||||
|
||||
|
||||
$idp_id = $url_parts['scheme'] . '://' . $url_parts['host'] . $port . ($url_parts['path'] ?? '');
|
||||
$sso_url = $idp_id . '?' . $url_parts['query'];
|
||||
if (!$returnUrl) {
|
||||
@@ -130,15 +133,15 @@ class Base extends AbstractService
|
||||
$query['ssoUrl'] = $sso_url;
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
return $sso_url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $requestInstance
|
||||
*
|
||||
* @return Response|LogoutResponse
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function receiveSamlResponse($requestInstance): Response|LogoutResponse
|
||||
{
|
||||
@@ -148,11 +151,11 @@ class Base extends AbstractService
|
||||
$messageContext = new MessageContext();
|
||||
$binding->receive($requestInstance, $messageContext);
|
||||
$deserializationContext = $messageContext->getDeserializationContext();
|
||||
|
||||
|
||||
// 获取响应
|
||||
/** @var Response|LogoutResponse $response */
|
||||
$response = $messageContext->getMessage();
|
||||
|
||||
|
||||
// 反序列化
|
||||
$response_type = get_class($response);
|
||||
switch ($response_type) {
|
||||
@@ -162,7 +165,7 @@ class Base extends AbstractService
|
||||
$response->deserialize($deserializationContext->getDocument(), $deserializationContext);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// 验签
|
||||
if ($type) {
|
||||
$crtFullName = config('saml.signature.directory') . config('saml.signature.crt');
|
||||
@@ -176,13 +179,13 @@ class Base extends AbstractService
|
||||
throw new LightSamlSecurityException('校验失败');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理响应状态
|
||||
$this->getStatusFromResponse($response);
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断响应状态
|
||||
*
|
||||
@@ -201,7 +204,7 @@ class Base extends AbstractService
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建SamlRequest
|
||||
*
|
||||
@@ -210,8 +213,8 @@ class Base extends AbstractService
|
||||
* @param string $idpID
|
||||
* @param string $issuer
|
||||
* @param string $relayState
|
||||
* @param bool $returnUrl
|
||||
* @param array $exactArguments
|
||||
* @param bool $returnUrl
|
||||
* @param array $exactArguments
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
@@ -226,7 +229,7 @@ class Base extends AbstractService
|
||||
): array|string {
|
||||
// $certificate = X509Certificate::fromFile('../cert/saml.crt');
|
||||
// $context = new SerializationContext();
|
||||
|
||||
|
||||
$logoutRequest = new LogoutRequest();
|
||||
$logoutRequest
|
||||
->setID(Helper::generateID())
|
||||
@@ -238,16 +241,15 @@ class Base extends AbstractService
|
||||
new NameID($uid)
|
||||
)->setSessionIndex($token);
|
||||
// ->setSignature(SignatureWriter::createByKeyAndCertificate($certificate, $privateKey))
|
||||
|
||||
|
||||
return $this->buildUrl($logoutRequest, $relayState, $exactArguments, $returnUrl);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 一次性获取(当前版本支持的)全部属性
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\Response $response
|
||||
*
|
||||
* @return \Singularity\HDK\Account\Resource\User
|
||||
* @param Response $response
|
||||
* @return User
|
||||
*/
|
||||
public function getAllAttributes(Response $response): User
|
||||
{
|
||||
@@ -260,7 +262,7 @@ class Base extends AbstractService
|
||||
'originToken' => $this->getTokenFromResponse($response) ?? $this->getTokenFromQuery(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从响应中获取uid
|
||||
*
|
||||
@@ -275,13 +277,98 @@ class Base extends AbstractService
|
||||
ClaimTypes::PPID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从响应中获取name
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNameFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取email
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEmailFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::EMAIL_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取头像
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAvatarFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
'avatar'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取pass
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getPassFromResponse(Response $response): bool
|
||||
{
|
||||
return (bool)$this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::UPN
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取 token
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function getTokenFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
'token'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 query 中获取 token
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTokenFromQuery(): ?string
|
||||
{
|
||||
return $this->request->query('token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\Response $response 响应
|
||||
* @param string $attributeName 属性名
|
||||
* @param bool $encrypted 是否需要解密
|
||||
* @param Response $response 响应
|
||||
* @param string $attributeName 属性名
|
||||
* @param bool $encrypted 是否需要解密
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
@@ -303,120 +390,35 @@ class Base extends AbstractService
|
||||
if (is_null($attribute)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return $attribute->getFirstAttributeValue();
|
||||
} catch (Throwable $throwable) {
|
||||
var_dump($throwable->getMessage());
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \LightSaml\Model\Assertion\EncryptedAssertionReader $encryptedAssertion
|
||||
* @param EncryptedAssertionReader $encryptedAssertion
|
||||
*
|
||||
* @return \LightSaml\Model\Assertion\Assertion
|
||||
* @return Assertion
|
||||
*/
|
||||
protected function decryptAssertionFromResponse(
|
||||
EncryptedAssertionReader $encryptedAssertion
|
||||
): Assertion {
|
||||
$certDir = config('saml.credential.directory');
|
||||
/** @var \RobRichards\XMLSecLibs\XMLSecurityKey $credential */
|
||||
/** @var XMLSecurityKey $credential */
|
||||
$credential = new X509Credential(
|
||||
X509Certificate::fromFile($certDir . config('saml.credential.crt')),
|
||||
KeyHelper::createPrivateKey($certDir . config('saml.credential.pem'), '', true)
|
||||
);
|
||||
return $encryptedAssertion->decryptMultiAssertion([$credential], new DeserializationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取name
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNameFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::NAME
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取email
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEmailFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::EMAIL_ADDRESS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取头像
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAvatarFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
'avatar'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取pass
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getPassFromResponse(Response $response): bool
|
||||
{
|
||||
return (bool)$this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
ClaimTypes::UPN
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中获取 token
|
||||
*
|
||||
* @param Response $response
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
public function getTokenFromResponse(Response $response): ?string
|
||||
{
|
||||
return $this->getAttributeValueFromResponse(
|
||||
$response,
|
||||
'token'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 query 中获取 token
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTokenFromQuery(): ?string
|
||||
{
|
||||
return $this->request->query('token');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 RelayState
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\Response $response
|
||||
* @param Response $response
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
@@ -424,4 +426,4 @@ class Base extends AbstractService
|
||||
{
|
||||
return $response->getRelayState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Services\Idp;
|
||||
|
||||
use DateTime;
|
||||
@@ -35,7 +36,7 @@ class AbstractLoginService extends AbstractService
|
||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
||||
*/
|
||||
private StdoutLogger $logger;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a SAML Response.
|
||||
*
|
||||
@@ -55,7 +56,7 @@ class AbstractLoginService extends AbstractService
|
||||
$statusCode = isset($user)
|
||||
? SamlConstants::STATUS_SUCCESS
|
||||
: SamlConstants::STATUS_NO_PASSIVE;
|
||||
|
||||
|
||||
$response = new Response();
|
||||
// 生成断言
|
||||
$assertion = $this->assertionBuilder(
|
||||
@@ -64,12 +65,12 @@ class AbstractLoginService extends AbstractService
|
||||
$messageId,
|
||||
$sp?->acsUrl ?? ''
|
||||
);
|
||||
|
||||
|
||||
// 加密
|
||||
$certificate = (new X509Certificate())->loadPem($sp->secret);
|
||||
$encryptedAssertion = new EncryptedAssertionWriter();
|
||||
$encryptedAssertion->encrypt($assertion, KeyHelper::createPublicKey($certificate));
|
||||
|
||||
|
||||
$response
|
||||
// ->addAssertion($assertion)
|
||||
->addEncryptedAssertion($encryptedAssertion)
|
||||
@@ -79,21 +80,21 @@ class AbstractLoginService extends AbstractService
|
||||
->setID(Helper::generateID())
|
||||
->setIssueInstant(new DateTime())
|
||||
->setDestination($sp->acsUrl);
|
||||
|
||||
|
||||
// Sign the response.
|
||||
// $response->setSignature(
|
||||
// new SignatureWriter($this->getCertificate(), $this->getPrivateKey())
|
||||
// );
|
||||
|
||||
|
||||
// Preparing the response XML
|
||||
$serializationContext = new SerializationContext();
|
||||
|
||||
|
||||
// Serialize to XML.
|
||||
$response->serialize($serializationContext->getDocument(), $serializationContext);
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 断言构建器
|
||||
*
|
||||
@@ -111,33 +112,33 @@ class AbstractLoginService extends AbstractService
|
||||
string $acsUrl = ''
|
||||
): Assertion {
|
||||
$assertion = new Assertion();
|
||||
|
||||
|
||||
$this->logger->info('生成 NameID: ' . $user?->uid ?? 'nil');
|
||||
// 构建基本信息
|
||||
$name_id = new NameID(
|
||||
$user?->uid ?? 'nil',
|
||||
SamlConstants::NAME_ID_FORMAT_PERSISTENT
|
||||
);
|
||||
|
||||
|
||||
$subject = new Subject();
|
||||
$subject_confirmation = new SubjectConfirmation();
|
||||
$subject_confirmation_data = new SubjectConfirmationData();
|
||||
|
||||
|
||||
$subject_confirmation_data = $subject_confirmation_data
|
||||
->setInResponseTo($messageId) # SAML 请求的标识
|
||||
->setNotOnOrAfter(new DateTime('+1 MINUTE')) # 有效期 1 分钟内
|
||||
->setRecipient($acsUrl); # 断言消费服务的地址
|
||||
|
||||
|
||||
/** @var SubjectConfirmation 验证数据 $subject_confirmation */
|
||||
$subject_confirmation = $subject_confirmation
|
||||
->setMethod(SamlConstants::CONFIRMATION_METHOD_BEARER)
|
||||
->setSubjectConfirmationData($subject_confirmation_data);
|
||||
|
||||
|
||||
/** @var Subject 主题/标题 $subject */
|
||||
$subject = $subject
|
||||
->setNameID($name_id)
|
||||
->addSubjectConfirmation($subject_confirmation);
|
||||
|
||||
|
||||
// 配置生效条件
|
||||
$conditions = new Conditions();
|
||||
$conditions = $conditions
|
||||
@@ -146,18 +147,18 @@ class AbstractLoginService extends AbstractService
|
||||
->addItem(
|
||||
new AudienceRestriction([$issuerId])# 受众限制
|
||||
);
|
||||
|
||||
|
||||
$assertion = $assertion
|
||||
->setId(Helper::generateID()) # 本次的 MessageID
|
||||
->setIssueInstant(new DateTime()) # 当前时间戳
|
||||
->setIssuer(new Issuer($this->idpId)) # 发信人——IDP的entityID
|
||||
->setSubject($subject)# 设置主题/标题
|
||||
->setConditions($conditions);
|
||||
|
||||
|
||||
// 补充更多内容
|
||||
if (isset($user)) {
|
||||
$attribute_statement = new AttributeStatement();
|
||||
|
||||
|
||||
// uid
|
||||
$attribute_statement = $attribute_statement
|
||||
->addAttribute(
|
||||
@@ -172,7 +173,7 @@ class AbstractLoginService extends AbstractService
|
||||
$user->uid ?? null
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// secEmail
|
||||
$attribute_statement = $attribute_statement
|
||||
->addAttribute(
|
||||
@@ -187,7 +188,7 @@ class AbstractLoginService extends AbstractService
|
||||
$user->secEmail ?? null
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// username
|
||||
$attribute_statement = $attribute_statement
|
||||
->addAttribute(
|
||||
@@ -202,7 +203,7 @@ class AbstractLoginService extends AbstractService
|
||||
$user->username ?? null
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// 密码
|
||||
$attribute_statement = $attribute_statement
|
||||
->addAttribute(
|
||||
@@ -216,7 +217,7 @@ class AbstractLoginService extends AbstractService
|
||||
!empty($user->password)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
//其他
|
||||
$attribute_statement = $attribute_statement
|
||||
->addAttribute(
|
||||
@@ -236,11 +237,11 @@ class AbstractLoginService extends AbstractService
|
||||
$user->avatar
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$assertion->addItem($attribute_statement);
|
||||
}
|
||||
|
||||
|
||||
return $assertion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,28 +12,31 @@ use LightSaml\Model\Protocol\LogoutRequest;
|
||||
use LightSaml\Model\Protocol\LogoutResponse;
|
||||
use LightSaml\Model\Protocol\Status;
|
||||
use LightSaml\Model\Protocol\StatusCode;
|
||||
use Singularity\HDK\Core\Constants\CommonErrorCode;
|
||||
use Singularity\HyperfSaml\Exceptions\Logout\ValidationException;
|
||||
use Singularity\HyperfSaml\Services\AbstractService;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
class AbstractLogoutService extends AbstractService
|
||||
{
|
||||
/**
|
||||
* 生成 saml 退出登录响应
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\StatusCode $statusCode
|
||||
* @param StatusCode $statusCode
|
||||
*
|
||||
* @param mixed|null $sp
|
||||
* @param string $messageId
|
||||
* @param mixed|null $sp
|
||||
* @param string $messageId
|
||||
*
|
||||
* @return \LightSaml\Model\Protocol\LogoutResponse
|
||||
* @return LogoutResponse
|
||||
*/
|
||||
public function createLogoutResponse(
|
||||
StatusCode $statusCode,
|
||||
mixed $sp,
|
||||
mixed $sp,
|
||||
string $messageId,
|
||||
): LogoutResponse {
|
||||
$response = new LogoutResponse();
|
||||
|
||||
|
||||
$response
|
||||
->setStatus(
|
||||
new Status($statusCode)
|
||||
@@ -43,25 +46,25 @@ class AbstractLogoutService extends AbstractService
|
||||
->setDestination($sp?->acsUrl)
|
||||
->setInResponseTo($messageId)
|
||||
->setIssuer(new Issuer(config('idp_id')));
|
||||
|
||||
|
||||
// Sign the response.
|
||||
// $response->setSignature(
|
||||
// new SignatureWriter($this->getCertificate(), $this->getPrivateKey())
|
||||
// );
|
||||
|
||||
|
||||
// Preparing the response XML
|
||||
$serializationContext = new SerializationContext();
|
||||
|
||||
|
||||
// Serialize to XML.
|
||||
$response->serialize($serializationContext->getDocument(), $serializationContext);
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从退出请求中解析用户信息
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\LogoutRequest $request
|
||||
* @param LogoutRequest $request
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -79,7 +82,7 @@ class AbstractLogoutService extends AbstractService
|
||||
$uid = $request->getNameID()?->getValue();
|
||||
/** @var string 用户传入所在客户端的 Session $sessionId */
|
||||
$sessionId = $request->getSessionIndex();
|
||||
|
||||
|
||||
// 校验
|
||||
if (empty($entityId)) {
|
||||
throw new ValidationException(
|
||||
@@ -88,7 +91,8 @@ class AbstractLogoutService extends AbstractService
|
||||
currentValue: $entityId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$sp = $this->findServiceProvider($entityId);
|
||||
if (empty($messageId)) {
|
||||
throw new ValidationException(
|
||||
@@ -97,7 +101,7 @@ class AbstractLogoutService extends AbstractService
|
||||
currentValue: $messageId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 90s 服务器之间时间差容错
|
||||
if (empty($logoutTime) || $logoutTime > time() + 90) {
|
||||
throw new ValidationException(
|
||||
@@ -122,7 +126,7 @@ class AbstractLogoutService extends AbstractService
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (empty($uid)) {
|
||||
throw new ValidationException(
|
||||
messageId: $messageId,
|
||||
@@ -131,11 +135,12 @@ class AbstractLogoutService extends AbstractService
|
||||
);
|
||||
}
|
||||
try {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$user = $this->userService->findUserWithUid($uid);
|
||||
} catch (ModelNotFoundException) {
|
||||
throw new LightSamlModelException(
|
||||
'用户不存在',
|
||||
ErrorCode::USER_NOT_FOUND
|
||||
CommonErrorCode::USER_NOT_FOUND
|
||||
);
|
||||
}
|
||||
if (empty($sessionId)) {
|
||||
@@ -144,10 +149,10 @@ class AbstractLogoutService extends AbstractService
|
||||
field: 'SessionIndex'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$user->token = $sessionId;
|
||||
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,14 +22,17 @@ use LightSaml\Model\Protocol\LogoutResponse;
|
||||
use LightSaml\Model\Protocol\Response;
|
||||
use LightSaml\SamlConstants;
|
||||
use Lmc\HttpConstants\Header;
|
||||
use Singularity\HDK\Account\Resource\User;
|
||||
use Singularity\HDK\Account\Services\Auth\AuthenticationInterface;
|
||||
use Singularity\HDK\Utils\Exceptions\Unauthorized;
|
||||
use RedisException;
|
||||
use Singularity\HDK\Auth\Resource\User;
|
||||
use Singularity\HDK\Auth\Services\AuthenticationInterface;
|
||||
use Singularity\HDK\Core\Exceptions\Unauthorized;
|
||||
use Singularity\HyperfSaml\Constants\SamlErrorCode;
|
||||
use Singularity\HyperfSaml\Exceptions\RuntimeException;
|
||||
use Singularity\HyperfSaml\Exceptions\ValidationException;
|
||||
use Singularity\HyperfSaml\Services\Base;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* 断言操作类
|
||||
* 用于获取用户登录状态
|
||||
@@ -42,19 +45,19 @@ use Singularity\HyperfSaml\Services\Base;
|
||||
class Assertion
|
||||
{
|
||||
public function __construct(
|
||||
private Base $base,
|
||||
private RequestInterface $request,
|
||||
private ResponseInterface $response,
|
||||
private AuthenticationInterface $authentication,
|
||||
private Redis $redis,
|
||||
private StdoutLoggerInterface $stdoutLogger
|
||||
private readonly Base $base,
|
||||
private readonly RequestInterface $request,
|
||||
private readonly ResponseInterface $response,
|
||||
private readonly AuthenticationInterface $authentication,
|
||||
private readonly Redis $redis,
|
||||
private readonly StdoutLoggerInterface $stdoutLogger
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ACS 解析响应
|
||||
*
|
||||
* @return \LightSaml\Model\Protocol\LogoutResponse|\LightSaml\Model\Protocol\Response
|
||||
* @return LogoutResponse|Response
|
||||
*/
|
||||
public function consumeParser(): Response|LogoutResponse
|
||||
{
|
||||
@@ -62,7 +65,7 @@ class Assertion
|
||||
if (empty($this->request->query('SAMLResponse'))) {
|
||||
throw new ValidationException(code: SamlErrorCode::AUTH_SAML_REQUEST_PARAMS_SAML_RESPONSE);
|
||||
}
|
||||
|
||||
|
||||
// 解析 SAML 请求
|
||||
$requestInstance = $this->base->requestParser($this->request);
|
||||
try {
|
||||
@@ -77,30 +80,30 @@ class Assertion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登录状态返回用户信息
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\Response $response
|
||||
* @param Response $response
|
||||
*
|
||||
* @return array|\Psr\Http\Message\ResponseInterface
|
||||
* @throws \RedisException
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function consumeResponse(Response $response): array|\Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$allow_multi_online = config('saml.allow_multi_online');
|
||||
$redis_prefix = config('common.redis.prefix');
|
||||
|
||||
|
||||
// Session 中的现有数据
|
||||
$oldUser = $this->authentication->getCurrentUser(returnNull: true)?->toArray() ?? [];
|
||||
// SSO 响应的过来的断言
|
||||
$user = $this->base->getAllAttributes($response)->toArray();
|
||||
|
||||
|
||||
$user = array_replace($oldUser, $user);
|
||||
|
||||
|
||||
// 更新
|
||||
$token = $this->authentication->generate(new User($user));
|
||||
|
||||
|
||||
// 据此判断是登录还是isLogin
|
||||
$relayState = $this->base->getRelayStateFromResponse($response);
|
||||
|
||||
@@ -146,14 +149,14 @@ class Assertion
|
||||
)->withHeader(Header::CONTENT_TYPE, 'text/html');
|
||||
// return $this->response->redirect($url, RFC7231::FOUND);
|
||||
}
|
||||
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录时跳转
|
||||
*
|
||||
* @param \LightSaml\Model\Protocol\LogoutResponse $response
|
||||
* @param LogoutResponse $response
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
@@ -161,7 +164,7 @@ class Assertion
|
||||
{
|
||||
$relayState = $response->getRelayState();
|
||||
$landing_url = config('saml.client.landing_host') . $relayState;
|
||||
|
||||
|
||||
$login_status_cookie = new Cookie(
|
||||
'is_login',
|
||||
"0",
|
||||
@@ -180,4 +183,4 @@ class Assertion
|
||||
)->withHeader(Header::CONTENT_TYPE, 'text/html');
|
||||
// return $this->response->redirect($landing_url, RFC7231::FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Services\Sp;
|
||||
|
||||
class MetadataProfile
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,13 @@ use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Hyperf\Redis\Redis;
|
||||
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
||||
use Singularity\HDK\Account\Services\Auth\AuthenticationInterface;
|
||||
use RedisException;
|
||||
use Singularity\HDK\Auth\Services\AuthenticationInterface;
|
||||
use Singularity\HyperfSaml\Services\Base;
|
||||
use Teapot\StatusCode\RFC\RFC7231;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* 单点退出登录
|
||||
* Singularity\HyperfSaml\Services\Sp\Slo@HyperfSaml
|
||||
@@ -30,58 +33,65 @@ use Teapot\StatusCode\RFC\RFC7231;
|
||||
class Slo
|
||||
{
|
||||
public function __construct(
|
||||
private Base $base,
|
||||
private RequestInterface $request,
|
||||
private ResponseInterface $response,
|
||||
private AuthenticationInterface $authentication,
|
||||
private Redis $redis
|
||||
private readonly Base $base,
|
||||
private readonly RequestInterface $request,
|
||||
private readonly ResponseInterface $response,
|
||||
private readonly AuthenticationInterface $authentication,
|
||||
private readonly Redis $redis,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重定向方式退出单点登录
|
||||
*
|
||||
* @param string $uid
|
||||
* @param string $originToken
|
||||
* @param string $uid
|
||||
* @param string $originToken
|
||||
* @param string|null $idpId
|
||||
* @param string|null $entityId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @return PsrResponseInterface
|
||||
*/
|
||||
public function redirect(string $uid, string $originToken): PsrResponseInterface
|
||||
{
|
||||
$idpId = config('saml.server.idp_logout_url');
|
||||
$issuer = config('saml.client.entity_id');
|
||||
public function redirect(
|
||||
string $uid,
|
||||
string $originToken,
|
||||
?string $entityId = null,
|
||||
?string $idpId = null,
|
||||
): PsrResponseInterface {
|
||||
$idpId ??= config('saml.server.idp_logout_url');
|
||||
$entityId ??= config('saml.client.entity_id');
|
||||
$this->authentication->invalidByToken();
|
||||
|
||||
|
||||
$relayState = $this->request->query('RelayState', '');
|
||||
|
||||
|
||||
$url = $this->base->createLogoutRequest(
|
||||
uid: $uid,
|
||||
token: $originToken,
|
||||
idpID: $idpId,
|
||||
issuer: $issuer,
|
||||
issuer: $entityId,
|
||||
relayState: $relayState
|
||||
);
|
||||
|
||||
|
||||
return $this->response->redirect($url, RFC7231::FOUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 回调方式退出单点登录(IDP 通知 SP)
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface Account 只根据 StatusCode 判断, 符合 [200, 300) 即可
|
||||
* @return PsrResponseInterface Account 只根据 StatusCode 判断, 符合 [200, 300) 即可
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function callback(): PsrResponseInterface
|
||||
{
|
||||
$redis_prefix = config('common.redis.prefix');
|
||||
|
||||
|
||||
$uid = $this->request->query('uid');
|
||||
$originToken = $this->request->query('token');
|
||||
|
||||
|
||||
if (empty($originToken)) {
|
||||
$this->authentication->invalidByUser($uid);
|
||||
} else {
|
||||
$key = "{$redis_prefix}user:token_map:$uid";
|
||||
|
||||
|
||||
$token = $this->redis->hGet($key, $originToken);
|
||||
$this->redis->hDel($key, $originToken);
|
||||
if ($token) {
|
||||
@@ -90,4 +100,4 @@ class Slo
|
||||
}
|
||||
return $this->response->raw('')->withStatus(RFC7231::NO_CONTENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Sso.php@HyperfSaml
|
||||
*
|
||||
@@ -11,19 +12,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace Singularity\HyperfSaml\Services\Sp;
|
||||
|
||||
use Hyperf\Utils\Codec\Json;
|
||||
use Singularity\HDK\Core\Constants\CommonErrorCode;
|
||||
use Singularity\HDK\Core\Exceptions\Forbidden;
|
||||
use Swoole\Exception;
|
||||
use Hyperf\Codec\Json;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Hyperf\Redis\Redis;
|
||||
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
||||
use RedisException;
|
||||
use Singularity\HDK\Core\Constants\CommonErrorCode;
|
||||
use Singularity\HDK\Core\Exceptions\Forbidden;
|
||||
use Singularity\HyperfSaml\Services\Base;
|
||||
use Swoole\Exception;
|
||||
use Teapot\StatusCode\RFC\RFC7231;
|
||||
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
/**
|
||||
* SP 单点登录
|
||||
* Singularity\HyperfSaml\Sp\Sso@HyperfSaml
|
||||
@@ -34,54 +37,68 @@ use Teapot\StatusCode\RFC\RFC7231;
|
||||
*/
|
||||
class Sso
|
||||
{
|
||||
private string $idpId;
|
||||
private string $idpAssertionUrl;
|
||||
private string $acsUrl;
|
||||
private string $issuer;
|
||||
|
||||
public function __construct(
|
||||
private Base $base,
|
||||
private RequestInterface $request,
|
||||
private ResponseInterface $response,
|
||||
private Redis $redis,
|
||||
private StdoutLoggerInterface $stdoutLogger
|
||||
) {
|
||||
$this->idpId = config('saml.server.idp_id');
|
||||
$this->idpAssertionUrl = config('saml.server.idp_assertion_url');
|
||||
|
||||
$this->issuer = config('saml.client.entity_id');
|
||||
$this->acsUrl = config('saml.client.acs_url');
|
||||
}
|
||||
|
||||
private readonly Base $base,
|
||||
private readonly RequestInterface $request,
|
||||
private readonly ResponseInterface $response,
|
||||
private readonly StdoutLoggerInterface $stdoutLogger,
|
||||
private readonly ?Redis $redis,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 自动重定向获取用户登录状态
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @param string|null $entityId
|
||||
* @param string|null $acsUrl
|
||||
* @param string|null $idpAssertionUrl
|
||||
*
|
||||
* @return PsrResponseInterface
|
||||
*/
|
||||
public function redirectSsoStatus(): PsrResponseInterface
|
||||
{
|
||||
public function redirectSsoStatus(
|
||||
?string $entityId = null,
|
||||
?string $acsUrl = null,
|
||||
?string $idpAssertionUrl = null,
|
||||
): PsrResponseInterface {
|
||||
$idpAssertionUrl ??= config('saml.server.idp_assertion_url');
|
||||
|
||||
$entityId ??= config('saml.client.entity_id');
|
||||
$acsUrl ??= config('saml.client.acs_url');
|
||||
$url = $this->base->createSamlRequest(
|
||||
idpID: $this->idpAssertionUrl,
|
||||
acsUrl: $this->acsUrl,
|
||||
issuer: $this->issuer,
|
||||
idpID: $idpAssertionUrl,
|
||||
acsUrl: $acsUrl,
|
||||
issuer: $entityId,
|
||||
);
|
||||
return $this->response->redirect($url, RFC7231::FOUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 自动重定向到单点登录
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @param string|null $entityId
|
||||
* @param string|null $acsUrl
|
||||
* @param string|null $idpId
|
||||
*
|
||||
* @return PsrResponseInterface
|
||||
*/
|
||||
public function redirectSso(): PsrResponseInterface
|
||||
{
|
||||
$language = $this->request->query('language') ?? config('language') ?? 'zh_CN';
|
||||
$relayState = $this->request->query('RelayState', '');
|
||||
|
||||
public function redirectSso(
|
||||
?string $entityId = null,
|
||||
?string $acsUrl = null,
|
||||
?string $idpId = null,
|
||||
): PsrResponseInterface {
|
||||
$idpId ??= config('saml.server.idp_id');
|
||||
|
||||
$entityId ??= config('saml.client.entity_id');
|
||||
$acsUrl ??= config('saml.client.acs_url');
|
||||
$language = $this->request->query(
|
||||
'language',
|
||||
config('translation.locale') ?? 'en'
|
||||
);
|
||||
$relayState = $this->request->query('RelayState', '/');
|
||||
|
||||
$url = $this->base->createSamlRequest(
|
||||
idpID: $this->idpId,
|
||||
acsUrl: $this->acsUrl,
|
||||
issuer: $this->issuer,
|
||||
idpID: $idpId,
|
||||
acsUrl: $acsUrl,
|
||||
issuer: $entityId,
|
||||
relayState: $relayState,
|
||||
exactArguments: [
|
||||
'language' => $language,
|
||||
@@ -89,7 +106,7 @@ class Sso
|
||||
);
|
||||
return $this->response->redirect($url, RFC7231::FOUND);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证用户同时在多个设备登录
|
||||
*
|
||||
@@ -97,26 +114,26 @@ class Sso
|
||||
* @param $token
|
||||
*
|
||||
* @return void
|
||||
* @throws \Singularity\HDK\Utils\Exceptions\Forbidden
|
||||
* @throws \RedisException
|
||||
* @throws Forbidden
|
||||
* @throws RedisException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function checkMultiDeviceOnline($uid, $token): void
|
||||
{
|
||||
$allow_multi_online = config('saml.allow_multi_online');
|
||||
|
||||
|
||||
// 只对限制为单设备登录的业务进行验证
|
||||
if ($allow_multi_online) {
|
||||
return;
|
||||
}
|
||||
$redis_prefix = config('common.redis.prefix');
|
||||
$key = "{$redis_prefix}user:token_map:{$uid}";
|
||||
$key = "{$redis_prefix}user:token_map:$uid";
|
||||
$type = $this->redis->type($key);
|
||||
|
||||
|
||||
try {
|
||||
$this->stdoutLogger->info('user_token redis type: ' . $type);
|
||||
switch ($type) {
|
||||
case SWOOLE_REDIS_TYPE_STRING:
|
||||
case 1:
|
||||
// 旧的值
|
||||
// 理论上只有刚切换的时候才会有此情况
|
||||
$last_token = $this->redis->get($key);
|
||||
@@ -124,19 +141,19 @@ class Sso
|
||||
throw new Exception(code: CommonErrorCode::AUTH_SESSION_CREATED_AT_ERROR);
|
||||
}
|
||||
break;
|
||||
case SWOOLE_REDIS_TYPE_HASH:
|
||||
case 5:
|
||||
$origin_token = $this->redis->hKeys($key);
|
||||
$this->stdoutLogger->info('user_token redis keys: ' . Json::encode($origin_token));
|
||||
$origin_token = array_pop($origin_token);
|
||||
$this->stdoutLogger->info('user_token redis key: ' . $origin_token);
|
||||
$latest_token = $this->redis->hGet($key, $origin_token);
|
||||
$this->stdoutLogger->info('user_token redis value isset: ' . $latest_token);
|
||||
|
||||
|
||||
if (empty($latest_token) || $latest_token !== $token) {
|
||||
throw new Exception(code: CommonErrorCode::AUTH_SESSION_CREATED_AT_ERROR);
|
||||
}
|
||||
break;
|
||||
case SWOOLE_REDIS_TYPE_NOT_FOUND:
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
throw new \Exception(code: CommonErrorCode::SERVER_ERROR);
|
||||
@@ -144,8 +161,8 @@ class Sso
|
||||
} catch (Exception $e) {
|
||||
$code = $e->getCode();
|
||||
$message = CommonErrorCode::getMessage($code);
|
||||
|
||||
|
||||
throw new Forbidden(code: $code, message: $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\Cases;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class AbstractTestCase.
|
||||
*/
|
||||
abstract class AbstractTestCase extends TestCase
|
||||
{
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\Cases;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExampleTest extends AbstractTestCase
|
||||
{
|
||||
public function testExample()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
5
tests/Feature/ExampleTest.php
Normal file
5
tests/Feature/ExampleTest.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('example', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
45
tests/Pest.php
Normal file
45
tests/Pest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||
| need to change it using the "uses()" function to bind a different classes or traits.
|
||||
|
|
||||
*/
|
||||
|
||||
// uses(Tests\TestCase::class)->in('Feature');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expectations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When you're writing tests, you often need to check that values meet certain conditions. The
|
||||
| "expect()" function gives you access to a set of "expectations" methods that you can use
|
||||
| to assert different things. Of course, you may extend the Expectation API at any time.
|
||||
|
|
||||
*/
|
||||
|
||||
expect()->extend('toBeOne', function () {
|
||||
return $this->toBe(1);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Functions
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
|
||||
| project that you don't want to repeat in every file. Here you can also expose helpers as
|
||||
| global functions to help you to reduce the number of lines of code in your test files.
|
||||
|
|
||||
*/
|
||||
|
||||
/*function something()
|
||||
{
|
||||
// ..
|
||||
}*/
|
||||
10
tests/TestCase.php
Normal file
10
tests/TestCase.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
//
|
||||
}
|
||||
5
tests/Unit/ExampleTest.php
Normal file
5
tests/Unit/ExampleTest.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('example', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
@@ -1,12 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
|
||||
|
||||
use Hyperf\Context\ApplicationContext;
|
||||
use Hyperf\Di\ClassLoader;
|
||||
use Hyperf\Di\Container;
|
||||
use Hyperf\Di\Definition\DefinitionSource;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Swoole\Runtime;
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
ini_set('display_startup_errors', 'on');
|
||||
|
||||
error_reporting(E_ALL);
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
|
||||
!defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
|
||||
!defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
|
||||
|
||||
Runtime::enableCoroutine(true);
|
||||
|
||||
require BASE_PATH . '/vendor/autoload.php';
|
||||
ClassLoader::init();
|
||||
|
||||
$container = new Container(new DefinitionSource([]));
|
||||
if (!$container instanceof ContainerInterface) {
|
||||
throw new RuntimeException('The dependency injection container is invalid.');
|
||||
}
|
||||
$container = ApplicationContext::setContainer($container);
|
||||
|
||||
Reference in New Issue
Block a user