mirror of
http://124.126.16.154:8888/singularity/hyperf-saml.git
synced 2026-01-15 03:45:06 +08:00
build: 增加lint/prettier/unitTest
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/vendor/
|
/vendor/
|
||||||
*.cache
|
*.cache
|
||||||
*.log
|
*.log
|
||||||
|
runtime/
|
||||||
|
|
||||||
# IDE support
|
# IDE support
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -1,89 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$header = <<<'EOF'
|
$finder = PhpCsFixer\Finder::create()->in([
|
||||||
This file is part of Hyperf.
|
__DIR__ . '/publish',
|
||||||
|
__DIR__ . '/src',
|
||||||
|
__DIR__ . '/tests',
|
||||||
|
]);
|
||||||
|
|
||||||
@link https://www.hyperf.io
|
$config = new PhpCsFixer\Config();
|
||||||
@document https://hyperf.wiki
|
return $config->setRules([
|
||||||
@contact group@hyperf.io
|
'@PSR12' => true,
|
||||||
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
'strict_param' => true,
|
||||||
EOF;
|
'array_syntax' => ['syntax' => 'short'],
|
||||||
|
])
|
||||||
return (new PhpCsFixer\Config())
|
->setUsingCache(false)
|
||||||
->setRiskyAllowed(true)
|
->setFinder($finder);
|
||||||
->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);
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.1",
|
||||||
|
"ext-redis": "*",
|
||||||
"hyperf/config": "3.1.*",
|
"hyperf/config": "3.1.*",
|
||||||
"hyperf/constants": "3.1.*",
|
"hyperf/constants": "3.1.*",
|
||||||
"hyperf/di": "3.1.*",
|
"hyperf/di": "3.1.*",
|
||||||
@@ -28,8 +29,7 @@
|
|||||||
"litesaml/lightsaml": "~3.0.0",
|
"litesaml/lightsaml": "~3.0.0",
|
||||||
"singularity/hdk-core": "^1.0.0",
|
"singularity/hdk-core": "^1.0.0",
|
||||||
"singularity/hdk-auth": "^1.0.0",
|
"singularity/hdk-auth": "^1.0.0",
|
||||||
"teapot/status-code": "^1.1",
|
"teapot/status-code": "^1.1"
|
||||||
"ext-redis": "*"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^3.0",
|
"friendsofphp/php-cs-fixer": "^3.0",
|
||||||
@@ -51,9 +51,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit -c phpunit.xml --colors=always",
|
"test": [
|
||||||
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
|
"rm -rf runtime",
|
||||||
"cs-fix": "php-cs-fixer fix $1"
|
"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": {
|
"extra": {
|
||||||
"hyperf": {
|
"hyperf": {
|
||||||
|
|||||||
1547
composer.lock
generated
1547
composer.lock
generated
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit bootstrap="tests/bootstrap.php"
|
<phpunit
|
||||||
backupGlobals="false"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
backupStaticAttributes="false"
|
bootstrap="tests/bootstrap.php"
|
||||||
verbose="true"
|
colors="true"
|
||||||
colors="true"
|
stopOnFailure="true"
|
||||||
convertErrorsToExceptions="true"
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd"
|
||||||
convertNoticesToExceptions="true"
|
cacheDirectory=".phpunit.cache"
|
||||||
convertWarningsToExceptions="true"
|
>
|
||||||
processIsolation="false"
|
<testsuites>
|
||||||
stopOnFailure="false">
|
<testsuite name="Testsuite">
|
||||||
<testsuite name="Testsuite">
|
<directory>./tests/</directory>
|
||||||
<directory>./tests/</directory>
|
</testsuite>
|
||||||
</testsuite>
|
</testsuites>
|
||||||
</phpunit>
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory>./src</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
</phpunit>
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ return [
|
|||||||
'saml_request' => 'SAMLRequest is required',
|
'saml_request' => 'SAMLRequest is required',
|
||||||
'saml_response' => 'SAMLResponse is required',
|
'saml_response' => 'SAMLResponse is required',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ return [
|
|||||||
'saml_request' => 'SAMLRequest 参数不能为空',
|
'saml_request' => 'SAMLRequest 参数不能为空',
|
||||||
'saml_response' => 'SAMLResponse 参数不能为空',
|
'saml_response' => 'SAMLResponse 参数不能为空',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,21 +2,23 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use function Hyperf\Support\env;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// 当前项目类型
|
// 当前项目类型
|
||||||
'type' => 'sp', // 可选值:sp/idp
|
'type' => 'sp', // 可选值:sp/idp
|
||||||
|
|
||||||
// 是否支持多用户同时在线
|
// 是否支持多用户同时在线
|
||||||
'allow_multi_online' => true,
|
'allow_multi_online' => true,
|
||||||
|
|
||||||
// IDP 相关配置
|
// IDP 相关配置
|
||||||
'server' => [
|
'server' => [
|
||||||
// common config
|
// common config
|
||||||
'idp_id' => env('IDP_ID', 'https://test-accountx.luxcreo.cn/api/v1/auth'),
|
'idp_id' => env('IDP_ID', 'https://test-accountx.luxcreo.cn/api/v1/auth'),
|
||||||
// 单点登录
|
// 单点登录
|
||||||
|
|
||||||
// idp config
|
// idp config
|
||||||
|
|
||||||
// sp config
|
// sp config
|
||||||
// 以下内容向服务端申请
|
// 以下内容向服务端申请
|
||||||
'idp_assertion_url' => env('IDP_ASSERTION_URL', 'https://test-accountx.luxcreo.cn/api/v1/auth/assertion'),
|
'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'),
|
'idp_logout_url' => env('IDP_LOGOUT_URL', 'https://test-accountx.luxcreo.cn/api/v1/slo'),
|
||||||
//单点退出
|
//单点退出
|
||||||
],
|
],
|
||||||
|
|
||||||
// SP 相关配置
|
// SP 相关配置
|
||||||
'client' => [
|
'client' => [
|
||||||
// sp config
|
// sp config
|
||||||
// 以下内容向服务端申请
|
// 以下内容向服务端申请
|
||||||
'entity_id' => env('ENTITY_ID', ''), // TODO 业务系统唯一标识
|
'entity_id' => env('ENTITY_ID', ''), // TODO 业务系统唯一标识
|
||||||
'acs_url' => env('ACS_URL', ''), // TODO 回调地址
|
'acs_url' => env('ACS_URL', ''), // TODO 回调地址
|
||||||
'landing_host' =>env('LANDING_HOST', ''), // TODO 站点 host
|
'landing_host' => env('LANDING_HOST', ''), // TODO 站点 host
|
||||||
],
|
],
|
||||||
|
|
||||||
// 证书
|
// 证书
|
||||||
'credential' => [
|
'credential' => [
|
||||||
'enable' => false,
|
'enable' => false,
|
||||||
@@ -42,4 +44,4 @@ return [
|
|||||||
'crt' => 'saml.crt',
|
'crt' => 'saml.crt',
|
||||||
'pem' => 'saml.pem',
|
'pem' => 'saml.pem',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
* Created on 2022/4/25
|
* Created on 2022/4/25
|
||||||
*/
|
*/
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Constants;
|
namespace Singularity\HyperfSaml\Constants;
|
||||||
|
|
||||||
use Hyperf\Constants\AbstractConstants;
|
use Hyperf\Constants\AbstractConstants;
|
||||||
@@ -23,27 +24,26 @@ use Hyperf\Constants\Annotation\Constants;
|
|||||||
*/
|
*/
|
||||||
class SamlErrorCode extends AbstractConstants
|
class SamlErrorCode extends AbstractConstants
|
||||||
{
|
{
|
||||||
|
|
||||||
// 203 SAML 鉴权
|
// 203 SAML 鉴权
|
||||||
/**
|
/**
|
||||||
* @Message("saml_error.default")
|
* @Message("saml_error.default")
|
||||||
*/
|
*/
|
||||||
public const AUTH_SAML_ERROR = 203000;
|
public const AUTH_SAML_ERROR = 203000;
|
||||||
|
|
||||||
// 20301 验证
|
// 20301 验证
|
||||||
/**
|
/**
|
||||||
* @Message("saml_error.params.default")
|
* @Message("saml_error.params.default")
|
||||||
*/
|
*/
|
||||||
public const AUTH_SAML_REQUEST_PARAMS_ERROR = 2030100;
|
public const AUTH_SAML_REQUEST_PARAMS_ERROR = 2030100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Message("saml_error.params.saml_request")
|
* @Message("saml_error.params.saml_request")
|
||||||
*/
|
*/
|
||||||
public const AUTH_SAML_REQUEST_PARAMS_SAML_REQUEST = 2030101;
|
public const AUTH_SAML_REQUEST_PARAMS_SAML_REQUEST = 2030101;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Message("saml_error.params.saml_response")
|
* @Message("saml_error.params.saml_response")
|
||||||
*/
|
*/
|
||||||
public const AUTH_SAML_REQUEST_PARAMS_SAML_RESPONSE = 2030102;
|
public const AUTH_SAML_REQUEST_PARAMS_SAML_RESPONSE = 2030102;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
||||||
|
|
||||||
use Hyperf\Di\Annotation\Inject;
|
use Hyperf\Di\Annotation\Inject;
|
||||||
@@ -16,6 +17,8 @@ use Singularity\HyperfSaml\Services\Idp\AbstractLoginService;
|
|||||||
use Singularity\HyperfSaml\Services\Idp\AbstractLogoutService;
|
use Singularity\HyperfSaml\Services\Idp\AbstractLogoutService;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IDP 相关错误捕获
|
* IDP 相关错误捕获
|
||||||
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
||||||
@@ -31,31 +34,31 @@ class SamlIdpHandler extends ExceptionHandler
|
|||||||
* @var \Singularity\HyperfSaml\Services\Base
|
* @var \Singularity\HyperfSaml\Services\Base
|
||||||
*/
|
*/
|
||||||
private Base $base;
|
private Base $base;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Inject
|
* @Inject
|
||||||
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLoginService
|
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLoginService
|
||||||
*/
|
*/
|
||||||
private AbstractLoginService $loginService;
|
private AbstractLoginService $loginService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Inject
|
* @Inject
|
||||||
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLogoutService
|
* @var \Singularity\HyperfSaml\Services\Idp\AbstractLogoutService
|
||||||
*/
|
*/
|
||||||
private AbstractLogoutService $logoutService;
|
private AbstractLogoutService $logoutService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Inject
|
* @Inject
|
||||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
* @var \Hyperf\Framework\Logger\StdoutLogger
|
||||||
*/
|
*/
|
||||||
private StdoutLogger $logger;
|
private StdoutLogger $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Inject(required=false)
|
* @Inject(required=false)
|
||||||
* @var \Hyperf\HttpServer\Contract\RequestInterface|null
|
* @var \Hyperf\HttpServer\Contract\RequestInterface|null
|
||||||
*/
|
*/
|
||||||
private ?RequestInterface $request;
|
private ?RequestInterface $request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param RuntimeException $throwable
|
* @param RuntimeException $throwable
|
||||||
* @param \Psr\Http\Message\ResponseInterface $response
|
* @param \Psr\Http\Message\ResponseInterface $response
|
||||||
@@ -67,7 +70,7 @@ class SamlIdpHandler extends ExceptionHandler
|
|||||||
{
|
{
|
||||||
// 阻止异常冒泡
|
// 阻止异常冒泡
|
||||||
$this->stopPropagation();
|
$this->stopPropagation();
|
||||||
|
|
||||||
// IDP 相关
|
// IDP 相关
|
||||||
if ($throwable instanceof ValidationException) {
|
if ($throwable instanceof ValidationException) {
|
||||||
$SAMLResponse = $this->logoutService->createLogoutResponse(
|
$SAMLResponse = $this->logoutService->createLogoutResponse(
|
||||||
@@ -84,7 +87,7 @@ class SamlIdpHandler extends ExceptionHandler
|
|||||||
messageId: $throwable->getMessageId(),
|
messageId: $throwable->getMessageId(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$error_type = get_class($throwable);
|
$error_type = get_class($throwable);
|
||||||
$data = [
|
$data = [
|
||||||
'errorCode' => $throwable->getCode(),
|
'errorCode' => $throwable->getCode(),
|
||||||
@@ -98,7 +101,7 @@ class SamlIdpHandler extends ExceptionHandler
|
|||||||
'sp' => config('saml.client.entity_id'),
|
'sp' => config('saml.client.entity_id'),
|
||||||
'messageId' => $throwable->getMessageId(),
|
'messageId' => $throwable->getMessageId(),
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
<<<ERROR_LOG
|
<<<ERROR_LOG
|
||||||
TYPE: $error_type
|
TYPE: $error_type
|
||||||
@@ -136,13 +139,13 @@ ERROR_LOG
|
|||||||
$throwable->getRelayState(),
|
$throwable->getRelayState(),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->logger->error("location: $url");
|
$this->logger->error("location: $url");
|
||||||
return $response
|
return $response
|
||||||
->withStatus(302)
|
->withStatus(302)
|
||||||
->withAddedHeader('Location', $url);
|
->withAddedHeader('Location', $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -150,4 +153,4 @@ ERROR_LOG
|
|||||||
{
|
{
|
||||||
return $throwable instanceof RuntimeException;
|
return $throwable instanceof RuntimeException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
namespace Singularity\HyperfSaml\Exceptions\Handler;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Hyperf\Codec\Json;
|
||||||
use Hyperf\Di\Annotation\Inject;
|
use Hyperf\Di\Annotation\Inject;
|
||||||
use Hyperf\ExceptionHandler\ExceptionHandler;
|
use Hyperf\ExceptionHandler\ExceptionHandler;
|
||||||
use Hyperf\Framework\Logger\StdoutLogger;
|
use Hyperf\Framework\Logger\StdoutLogger;
|
||||||
use Hyperf\HttpMessage\Stream\SwooleStream;
|
use Hyperf\HttpMessage\Stream\SwooleStream;
|
||||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||||
use Hyperf\Utils\Codec\Json;
|
|
||||||
use Lmc\HttpConstants\Header;
|
use Lmc\HttpConstants\Header;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Singularity\HyperfSaml\Constants\SamlErrorCode;
|
use Singularity\HyperfSaml\Constants\SamlErrorCode;
|
||||||
@@ -17,6 +19,8 @@ use Singularity\HyperfSaml\Exceptions\ValidationException;
|
|||||||
use Teapot\StatusCode\RFC\RFC7231;
|
use Teapot\StatusCode\RFC\RFC7231;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SP 相关错误捕获
|
* SP 相关错误捕获
|
||||||
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
* Singularity\HyperfSaml\Exceptions\Handler\SamlIdpHandler@HyperfSaml
|
||||||
@@ -29,45 +33,45 @@ class SamlSpHandler extends ExceptionHandler
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @Inject
|
* @Inject
|
||||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
* @var StdoutLogger
|
||||||
*/
|
*/
|
||||||
private StdoutLogger $logger;
|
private StdoutLogger $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Inject(required=false)
|
* @Inject(required=false)
|
||||||
* @var \Hyperf\HttpServer\Contract\RequestInterface|null
|
* @var RequestInterface|null
|
||||||
*/
|
*/
|
||||||
private ?RequestInterface $request;
|
private ?RequestInterface $request;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param RuntimeException $throwable
|
* @param RuntimeException $throwable
|
||||||
* @param \Psr\Http\Message\ResponseInterface $response
|
* @param ResponseInterface $response
|
||||||
*
|
*
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
* @return ResponseInterface
|
||||||
* @throws \Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
public function handle(Throwable $throwable, ResponseInterface $response): ResponseInterface
|
||||||
{
|
{
|
||||||
// 阻止异常冒泡
|
// 阻止异常冒泡
|
||||||
$this->stopPropagation();
|
$this->stopPropagation();
|
||||||
|
|
||||||
$restful = config('common.response.restful');
|
$restful = config('common.response.restful');
|
||||||
$code_name = config('common.response.code_name');
|
$code_name = config('common.response.code_name');
|
||||||
$message_name = config('common.response.message_name');
|
$message_name = config('common.response.message_name');
|
||||||
|
|
||||||
$this->request?->url();
|
$this->request?->url();
|
||||||
|
|
||||||
$error_type = get_class($throwable);
|
$error_type = get_class($throwable);
|
||||||
|
|
||||||
$is_testing = config('app_status') === true;
|
$is_testing = config('app_status') === true;
|
||||||
$is_debug = $this->request?->hasHeader('Postman-Token')
|
$is_debug = $this->request?->hasHeader('Postman-Token')
|
||||||
|| $this->request?->header('User-Agent') === 'apifox/2.1.8 (https://www.apifox.cn)';
|
|| $this->request?->header('User-Agent') === 'apifox/2.1.8 (https://www.apifox.cn)';
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
$code_name => $throwable->getCode(),
|
$code_name => $throwable->getCode(),
|
||||||
$message_name => $throwable->getMessage(),
|
$message_name => $throwable->getMessage(),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 验证失败
|
// 验证失败
|
||||||
if ($throwable instanceof ValidationException) {
|
if ($throwable instanceof ValidationException) {
|
||||||
$code = $throwable->getCode();
|
$code = $throwable->getCode();
|
||||||
@@ -82,8 +86,8 @@ class SamlSpHandler extends ExceptionHandler
|
|||||||
$data['availableValue'] = $throwable->getAvailableValue();
|
$data['availableValue'] = $throwable->getAvailableValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 整理日志所需的数据
|
// 整理日志所需的数据
|
||||||
$request_time = date('Y-m-d H:i:s');
|
$request_time = date('Y-m-d H:i:s');
|
||||||
$request_headers = $this->request->getHeaders();
|
$request_headers = $this->request->getHeaders();
|
||||||
@@ -93,20 +97,19 @@ class SamlSpHandler extends ExceptionHandler
|
|||||||
'sp' => config('saml.client.entity_id'),
|
'sp' => config('saml.client.entity_id'),
|
||||||
'messageId' => $throwable->getMessageId(),
|
'messageId' => $throwable->getMessageId(),
|
||||||
], JSON_UNESCAPED_UNICODE);
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
|
||||||
$response = $response->withHeader(
|
$response = $response->withHeader(
|
||||||
Header::CONTENT_TYPE,
|
Header::CONTENT_TYPE,
|
||||||
'application/json; charset=utf-8'
|
'application/json; charset=utf-8'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($is_debug && $is_testing) {
|
if ($is_debug && $is_testing) {
|
||||||
$data['trace'] = [
|
$data['trace'] = [
|
||||||
'errorType' => $error_type,
|
'errorType' => $error_type,
|
||||||
'errorTrack' => $throwable->getTrace(),
|
'errorTrack' => $throwable->getTrace(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$cookies = json_encode($this->request->getCookieParams(), JSON_UNESCAPED_UNICODE);
|
|
||||||
$this->logger->error(
|
$this->logger->error(
|
||||||
<<<ERROR_LOG
|
<<<ERROR_LOG
|
||||||
TYPE: $error_type
|
TYPE: $error_type
|
||||||
@@ -139,11 +142,10 @@ TRACE:
|
|||||||
|
|
||||||
ERROR_LOG
|
ERROR_LOG
|
||||||
);
|
);
|
||||||
|
|
||||||
$data = Json::encode($data);
|
$data = Json::encode($data);
|
||||||
if ($restful) {
|
if ($restful) {
|
||||||
$response = $response->withStatus(
|
$response = $response->withStatus(
|
||||||
$status_code ??
|
|
||||||
$throwable->status ??
|
$throwable->status ??
|
||||||
$throwable->statusCode ??
|
$throwable->statusCode ??
|
||||||
RFC7231::INTERNAL_SERVER_ERROR
|
RFC7231::INTERNAL_SERVER_ERROR
|
||||||
@@ -154,7 +156,7 @@ ERROR_LOG
|
|||||||
new SwooleStream($data)
|
new SwooleStream($data)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -162,4 +164,4 @@ ERROR_LOG
|
|||||||
{
|
{
|
||||||
return $throwable instanceof RuntimeException;
|
return $throwable instanceof RuntimeException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Exceptions\Logout;
|
namespace Singularity\HyperfSaml\Exceptions\Logout;
|
||||||
|
|
||||||
use Singularity\HyperfSaml\Exceptions\ValidationException as SAMLValidationException;
|
use Singularity\HyperfSaml\Exceptions\ValidationException as SAMLValidationException;
|
||||||
|
|
||||||
class ValidationException extends SAMLValidationException
|
class ValidationException extends SAMLValidationException
|
||||||
{
|
{
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Exceptions;
|
namespace Singularity\HyperfSaml\Exceptions;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -17,7 +18,7 @@ class RuntimeException extends \RuntimeException
|
|||||||
) {
|
) {
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +26,7 @@ class RuntimeException extends \RuntimeException
|
|||||||
{
|
{
|
||||||
return $this->statusCode;
|
return $this->statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -33,7 +34,7 @@ class RuntimeException extends \RuntimeException
|
|||||||
{
|
{
|
||||||
return $this->relayState;
|
return $this->relayState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -41,4 +42,4 @@ class RuntimeException extends \RuntimeException
|
|||||||
{
|
{
|
||||||
return $this->messageId;
|
return $this->messageId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Exceptions;
|
namespace Singularity\HyperfSaml\Exceptions;
|
||||||
|
|
||||||
// use App\Model\ServiceProvider;
|
// use App\Model\ServiceProvider;
|
||||||
@@ -29,17 +30,17 @@ class ValidationException extends RuntimeException
|
|||||||
SamlConstants::STATUS_INVALID_NAME_ID_POLICY,
|
SamlConstants::STATUS_INVALID_NAME_ID_POLICY,
|
||||||
$messageId,
|
$messageId,
|
||||||
$relayState,
|
$relayState,
|
||||||
$message ?? SamlErrorCode::getMessage($code) ,
|
$message ?? SamlErrorCode::getMessage($code),
|
||||||
$code,
|
$code,
|
||||||
$previous
|
$previous
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFieldName(): string
|
public function getFieldName(): string
|
||||||
{
|
{
|
||||||
return $this->field;
|
return $this->field;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@@ -47,7 +48,7 @@ class ValidationException extends RuntimeException
|
|||||||
{
|
{
|
||||||
return $this->currentValue;
|
return $this->currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@@ -55,4 +56,4 @@ class ValidationException extends RuntimeException
|
|||||||
{
|
{
|
||||||
return $this->availableValue;
|
return $this->availableValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ abstract class AbstractService
|
|||||||
try {
|
try {
|
||||||
$bindingFactory = new BindingFactory();
|
$bindingFactory = new BindingFactory();
|
||||||
$binding = $bindingFactory->getBindingByRequest($requestInstance);
|
$binding = $bindingFactory->getBindingByRequest($requestInstance);
|
||||||
|
|
||||||
// We prepare a message context to receive our SAML Request message.
|
// We prepare a message context to receive our SAML Request message.
|
||||||
$messageContext = new MessageContext();
|
$messageContext = new MessageContext();
|
||||||
|
|
||||||
// The received method fills in the messageContext with the SAML Request data.
|
// The received method fills in the messageContext with the SAML Request data.
|
||||||
/** @var \LightSaml\Model\Protocol\Response $response */
|
/** @var \LightSaml\Model\Protocol\Response $response */
|
||||||
$binding->receive($requestInstance, $messageContext);
|
$binding->receive($requestInstance, $messageContext);
|
||||||
|
|
||||||
return $messageContext;
|
return $messageContext;
|
||||||
} catch (InvalidArgumentException $exception) {
|
} catch (InvalidArgumentException $exception) {
|
||||||
throw new ValidationException(
|
throw new ValidationException(
|
||||||
@@ -68,7 +68,7 @@ abstract class AbstractService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应给 ACS
|
* 响应给 ACS
|
||||||
*
|
*
|
||||||
@@ -93,10 +93,10 @@ abstract class AbstractService
|
|||||||
}
|
}
|
||||||
$messageContext = new MessageContext();
|
$messageContext = new MessageContext();
|
||||||
$messageContext->setMessage($responseInstance);
|
$messageContext->setMessage($responseInstance);
|
||||||
|
|
||||||
$bindingFactory = new BindingFactory();
|
$bindingFactory = new BindingFactory();
|
||||||
$redirectBinding = $bindingFactory->create($bindingType);
|
$redirectBinding = $bindingFactory->create($bindingType);
|
||||||
|
|
||||||
// Ensure we include the RelayState.
|
// Ensure we include the RelayState.
|
||||||
$message = $messageContext->getMessage();
|
$message = $messageContext->getMessage();
|
||||||
$rs = $message->getRelayState();
|
$rs = $message->getRelayState();
|
||||||
@@ -104,29 +104,29 @@ abstract class AbstractService
|
|||||||
$message->setRelayState($relayState);
|
$message->setRelayState($relayState);
|
||||||
$messageContext->setMessage($message);
|
$messageContext->setMessage($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the Response.
|
// Return the Response.
|
||||||
/** @var \Symfony\Component\HttpFoundation\RedirectResponse $httpResponse */
|
/** @var \Symfony\Component\HttpFoundation\RedirectResponse $httpResponse */
|
||||||
$httpResponse = $redirectBinding->send($messageContext);
|
$httpResponse = $redirectBinding->send($messageContext);
|
||||||
|
|
||||||
$url_parts = parse_url($httpResponse->getTargetUrl());
|
$url_parts = parse_url($httpResponse->getTargetUrl());
|
||||||
|
|
||||||
$query = [];
|
$query = [];
|
||||||
parse_str($url_parts['query'], $query);
|
parse_str($url_parts['query'], $query);
|
||||||
// $query['Signature'] = $this->base64Wrapper->encode($query['Signature']);
|
// $query['Signature'] = $this->base64Wrapper->encode($query['Signature']);
|
||||||
|
|
||||||
$query += $extraParams;
|
$query += $extraParams;
|
||||||
$url_parts['query'] = http_build_query($query);
|
$url_parts['query'] = http_build_query($query);
|
||||||
$url_parts['port'] ??= '80';
|
$url_parts['port'] ??= '80';
|
||||||
if ($url_parts['port'] !== '80') {
|
if ($url_parts['port'] !== '80') {
|
||||||
$url_parts['host'] .= ':' . $url_parts['port'];
|
$url_parts['host'] .= ':' . $url_parts['port'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];
|
$url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'] . '?' . $url_parts['query'];
|
||||||
if (!$returnHtml) {
|
if (!$returnHtml) {
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<!DOCTYPE html>
|
'<!DOCTYPE html>
|
||||||
<html lang="">
|
<html lang="">
|
||||||
@@ -142,7 +142,7 @@ abstract class AbstractService
|
|||||||
);
|
);
|
||||||
// return $httpResponse->setTargetUrl($url)->getContent();
|
// return $httpResponse->setTargetUrl($url)->getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function requestParser($request): Request
|
public function requestParser($request): Request
|
||||||
{
|
{
|
||||||
$params = $request->getQueryParams();
|
$params = $request->getQueryParams();
|
||||||
@@ -152,7 +152,7 @@ abstract class AbstractService
|
|||||||
$params,
|
$params,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the certificate from the IdP.
|
* Retrieves the certificate from the IdP.
|
||||||
*
|
*
|
||||||
@@ -162,7 +162,7 @@ abstract class AbstractService
|
|||||||
{
|
{
|
||||||
return X509Certificate::fromFile('cert/server.crt');
|
return X509Certificate::fromFile('cert/server.crt');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the private key from the Idp.
|
* Retrieves the private key from the Idp.
|
||||||
*
|
*
|
||||||
@@ -172,4 +172,4 @@ abstract class AbstractService
|
|||||||
{
|
{
|
||||||
return KeyHelper::createPrivateKey('cert/server.key', '', true);
|
return KeyHelper::createPrivateKey('cert/server.key', '', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ use RobRichards\XMLSecLibs\XMLSecurityKey;
|
|||||||
use Singularity\HDK\Auth\Resource\User;
|
use Singularity\HDK\Auth\Resource\User;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singularity\HyperfSaml\Lib\Base@HyperfSaml
|
* Singularity\HyperfSaml\Lib\Base@HyperfSaml
|
||||||
*
|
*
|
||||||
@@ -424,4 +426,4 @@ class Base extends AbstractService
|
|||||||
{
|
{
|
||||||
return $response->getRelayState();
|
return $response->getRelayState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Services\Idp;
|
namespace Singularity\HyperfSaml\Services\Idp;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
@@ -35,7 +36,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
* @var \Hyperf\Framework\Logger\StdoutLogger
|
* @var \Hyperf\Framework\Logger\StdoutLogger
|
||||||
*/
|
*/
|
||||||
private StdoutLogger $logger;
|
private StdoutLogger $logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a SAML Response.
|
* Constructs a SAML Response.
|
||||||
*
|
*
|
||||||
@@ -55,7 +56,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
$statusCode = isset($user)
|
$statusCode = isset($user)
|
||||||
? SamlConstants::STATUS_SUCCESS
|
? SamlConstants::STATUS_SUCCESS
|
||||||
: SamlConstants::STATUS_NO_PASSIVE;
|
: SamlConstants::STATUS_NO_PASSIVE;
|
||||||
|
|
||||||
$response = new Response();
|
$response = new Response();
|
||||||
// 生成断言
|
// 生成断言
|
||||||
$assertion = $this->assertionBuilder(
|
$assertion = $this->assertionBuilder(
|
||||||
@@ -64,12 +65,12 @@ class AbstractLoginService extends AbstractService
|
|||||||
$messageId,
|
$messageId,
|
||||||
$sp?->acsUrl ?? ''
|
$sp?->acsUrl ?? ''
|
||||||
);
|
);
|
||||||
|
|
||||||
// 加密
|
// 加密
|
||||||
$certificate = (new X509Certificate())->loadPem($sp->secret);
|
$certificate = (new X509Certificate())->loadPem($sp->secret);
|
||||||
$encryptedAssertion = new EncryptedAssertionWriter();
|
$encryptedAssertion = new EncryptedAssertionWriter();
|
||||||
$encryptedAssertion->encrypt($assertion, KeyHelper::createPublicKey($certificate));
|
$encryptedAssertion->encrypt($assertion, KeyHelper::createPublicKey($certificate));
|
||||||
|
|
||||||
$response
|
$response
|
||||||
// ->addAssertion($assertion)
|
// ->addAssertion($assertion)
|
||||||
->addEncryptedAssertion($encryptedAssertion)
|
->addEncryptedAssertion($encryptedAssertion)
|
||||||
@@ -79,21 +80,21 @@ class AbstractLoginService extends AbstractService
|
|||||||
->setID(Helper::generateID())
|
->setID(Helper::generateID())
|
||||||
->setIssueInstant(new DateTime())
|
->setIssueInstant(new DateTime())
|
||||||
->setDestination($sp->acsUrl);
|
->setDestination($sp->acsUrl);
|
||||||
|
|
||||||
// Sign the response.
|
// Sign the response.
|
||||||
// $response->setSignature(
|
// $response->setSignature(
|
||||||
// new SignatureWriter($this->getCertificate(), $this->getPrivateKey())
|
// new SignatureWriter($this->getCertificate(), $this->getPrivateKey())
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// Preparing the response XML
|
// Preparing the response XML
|
||||||
$serializationContext = new SerializationContext();
|
$serializationContext = new SerializationContext();
|
||||||
|
|
||||||
// Serialize to XML.
|
// Serialize to XML.
|
||||||
$response->serialize($serializationContext->getDocument(), $serializationContext);
|
$response->serialize($serializationContext->getDocument(), $serializationContext);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断言构建器
|
* 断言构建器
|
||||||
*
|
*
|
||||||
@@ -111,33 +112,33 @@ class AbstractLoginService extends AbstractService
|
|||||||
string $acsUrl = ''
|
string $acsUrl = ''
|
||||||
): Assertion {
|
): Assertion {
|
||||||
$assertion = new Assertion();
|
$assertion = new Assertion();
|
||||||
|
|
||||||
$this->logger->info('生成 NameID: ' . $user?->uid ?? 'nil');
|
$this->logger->info('生成 NameID: ' . $user?->uid ?? 'nil');
|
||||||
// 构建基本信息
|
// 构建基本信息
|
||||||
$name_id = new NameID(
|
$name_id = new NameID(
|
||||||
$user?->uid ?? 'nil',
|
$user?->uid ?? 'nil',
|
||||||
SamlConstants::NAME_ID_FORMAT_PERSISTENT
|
SamlConstants::NAME_ID_FORMAT_PERSISTENT
|
||||||
);
|
);
|
||||||
|
|
||||||
$subject = new Subject();
|
$subject = new Subject();
|
||||||
$subject_confirmation = new SubjectConfirmation();
|
$subject_confirmation = new SubjectConfirmation();
|
||||||
$subject_confirmation_data = new SubjectConfirmationData();
|
$subject_confirmation_data = new SubjectConfirmationData();
|
||||||
|
|
||||||
$subject_confirmation_data = $subject_confirmation_data
|
$subject_confirmation_data = $subject_confirmation_data
|
||||||
->setInResponseTo($messageId) # SAML 请求的标识
|
->setInResponseTo($messageId) # SAML 请求的标识
|
||||||
->setNotOnOrAfter(new DateTime('+1 MINUTE')) # 有效期 1 分钟内
|
->setNotOnOrAfter(new DateTime('+1 MINUTE')) # 有效期 1 分钟内
|
||||||
->setRecipient($acsUrl); # 断言消费服务的地址
|
->setRecipient($acsUrl); # 断言消费服务的地址
|
||||||
|
|
||||||
/** @var SubjectConfirmation 验证数据 $subject_confirmation */
|
/** @var SubjectConfirmation 验证数据 $subject_confirmation */
|
||||||
$subject_confirmation = $subject_confirmation
|
$subject_confirmation = $subject_confirmation
|
||||||
->setMethod(SamlConstants::CONFIRMATION_METHOD_BEARER)
|
->setMethod(SamlConstants::CONFIRMATION_METHOD_BEARER)
|
||||||
->setSubjectConfirmationData($subject_confirmation_data);
|
->setSubjectConfirmationData($subject_confirmation_data);
|
||||||
|
|
||||||
/** @var Subject 主题/标题 $subject */
|
/** @var Subject 主题/标题 $subject */
|
||||||
$subject = $subject
|
$subject = $subject
|
||||||
->setNameID($name_id)
|
->setNameID($name_id)
|
||||||
->addSubjectConfirmation($subject_confirmation);
|
->addSubjectConfirmation($subject_confirmation);
|
||||||
|
|
||||||
// 配置生效条件
|
// 配置生效条件
|
||||||
$conditions = new Conditions();
|
$conditions = new Conditions();
|
||||||
$conditions = $conditions
|
$conditions = $conditions
|
||||||
@@ -146,18 +147,18 @@ class AbstractLoginService extends AbstractService
|
|||||||
->addItem(
|
->addItem(
|
||||||
new AudienceRestriction([$issuerId])# 受众限制
|
new AudienceRestriction([$issuerId])# 受众限制
|
||||||
);
|
);
|
||||||
|
|
||||||
$assertion = $assertion
|
$assertion = $assertion
|
||||||
->setId(Helper::generateID()) # 本次的 MessageID
|
->setId(Helper::generateID()) # 本次的 MessageID
|
||||||
->setIssueInstant(new DateTime()) # 当前时间戳
|
->setIssueInstant(new DateTime()) # 当前时间戳
|
||||||
->setIssuer(new Issuer($this->idpId)) # 发信人——IDP的entityID
|
->setIssuer(new Issuer($this->idpId)) # 发信人——IDP的entityID
|
||||||
->setSubject($subject)# 设置主题/标题
|
->setSubject($subject)# 设置主题/标题
|
||||||
->setConditions($conditions);
|
->setConditions($conditions);
|
||||||
|
|
||||||
// 补充更多内容
|
// 补充更多内容
|
||||||
if (isset($user)) {
|
if (isset($user)) {
|
||||||
$attribute_statement = new AttributeStatement();
|
$attribute_statement = new AttributeStatement();
|
||||||
|
|
||||||
// uid
|
// uid
|
||||||
$attribute_statement = $attribute_statement
|
$attribute_statement = $attribute_statement
|
||||||
->addAttribute(
|
->addAttribute(
|
||||||
@@ -172,7 +173,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
$user->uid ?? null
|
$user->uid ?? null
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// secEmail
|
// secEmail
|
||||||
$attribute_statement = $attribute_statement
|
$attribute_statement = $attribute_statement
|
||||||
->addAttribute(
|
->addAttribute(
|
||||||
@@ -187,7 +188,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
$user->secEmail ?? null
|
$user->secEmail ?? null
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// username
|
// username
|
||||||
$attribute_statement = $attribute_statement
|
$attribute_statement = $attribute_statement
|
||||||
->addAttribute(
|
->addAttribute(
|
||||||
@@ -202,7 +203,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
$user->username ?? null
|
$user->username ?? null
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 密码
|
// 密码
|
||||||
$attribute_statement = $attribute_statement
|
$attribute_statement = $attribute_statement
|
||||||
->addAttribute(
|
->addAttribute(
|
||||||
@@ -216,7 +217,7 @@ class AbstractLoginService extends AbstractService
|
|||||||
!empty($user->password)
|
!empty($user->password)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
//其他
|
//其他
|
||||||
$attribute_statement = $attribute_statement
|
$attribute_statement = $attribute_statement
|
||||||
->addAttribute(
|
->addAttribute(
|
||||||
@@ -236,11 +237,11 @@ class AbstractLoginService extends AbstractService
|
|||||||
$user->avatar
|
$user->avatar
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$assertion->addItem($attribute_statement);
|
$assertion->addItem($attribute_statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $assertion;
|
return $assertion;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use Singularity\HDK\Core\Constants\CommonErrorCode;
|
|||||||
use Singularity\HyperfSaml\Exceptions\Logout\ValidationException;
|
use Singularity\HyperfSaml\Exceptions\Logout\ValidationException;
|
||||||
use Singularity\HyperfSaml\Services\AbstractService;
|
use Singularity\HyperfSaml\Services\AbstractService;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
class AbstractLogoutService extends AbstractService
|
class AbstractLogoutService extends AbstractService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -153,4 +155,4 @@ class AbstractLogoutService extends AbstractService
|
|||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ use Singularity\HyperfSaml\Exceptions\RuntimeException;
|
|||||||
use Singularity\HyperfSaml\Exceptions\ValidationException;
|
use Singularity\HyperfSaml\Exceptions\ValidationException;
|
||||||
use Singularity\HyperfSaml\Services\Base;
|
use Singularity\HyperfSaml\Services\Base;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断言操作类
|
* 断言操作类
|
||||||
* 用于获取用户登录状态
|
* 用于获取用户登录状态
|
||||||
@@ -181,4 +183,4 @@ class Assertion
|
|||||||
)->withHeader(Header::CONTENT_TYPE, 'text/html');
|
)->withHeader(Header::CONTENT_TYPE, 'text/html');
|
||||||
// return $this->response->redirect($landing_url, RFC7231::FOUND);
|
// return $this->response->redirect($landing_url, RFC7231::FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Singularity\HyperfSaml\Services\Sp;
|
namespace Singularity\HyperfSaml\Services\Sp;
|
||||||
|
|
||||||
class MetadataProfile
|
class MetadataProfile
|
||||||
{
|
{
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,10 +15,13 @@ use Hyperf\HttpServer\Contract\RequestInterface;
|
|||||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||||
use Hyperf\Redis\Redis;
|
use Hyperf\Redis\Redis;
|
||||||
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
||||||
|
use RedisException;
|
||||||
use Singularity\HDK\Auth\Services\AuthenticationInterface;
|
use Singularity\HDK\Auth\Services\AuthenticationInterface;
|
||||||
use Singularity\HyperfSaml\Services\Base;
|
use Singularity\HyperfSaml\Services\Base;
|
||||||
use Teapot\StatusCode\RFC\RFC7231;
|
use Teapot\StatusCode\RFC\RFC7231;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单点退出登录
|
* 单点退出登录
|
||||||
* Singularity\HyperfSaml\Services\Sp\Slo@HyperfSaml
|
* Singularity\HyperfSaml\Services\Sp\Slo@HyperfSaml
|
||||||
@@ -30,30 +33,30 @@ use Teapot\StatusCode\RFC\RFC7231;
|
|||||||
class Slo
|
class Slo
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Base $base,
|
private readonly Base $base,
|
||||||
private RequestInterface $request,
|
private readonly RequestInterface $request,
|
||||||
private ResponseInterface $response,
|
private readonly ResponseInterface $response,
|
||||||
private AuthenticationInterface $authentication,
|
private readonly AuthenticationInterface $authentication,
|
||||||
private Redis $redis
|
private readonly Redis $redis
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重定向方式退出单点登录
|
* 重定向方式退出单点登录
|
||||||
*
|
*
|
||||||
* @param string $uid
|
* @param string $uid
|
||||||
* @param string $originToken
|
* @param string $originToken
|
||||||
*
|
*
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
* @return PsrResponseInterface
|
||||||
*/
|
*/
|
||||||
public function redirect(string $uid, string $originToken): PsrResponseInterface
|
public function redirect(string $uid, string $originToken): PsrResponseInterface
|
||||||
{
|
{
|
||||||
$idpId = config('saml.server.idp_logout_url');
|
$idpId = config('saml.server.idp_logout_url');
|
||||||
$issuer = config('saml.client.entity_id');
|
$issuer = config('saml.client.entity_id');
|
||||||
$this->authentication->invalidByToken();
|
$this->authentication->invalidByToken();
|
||||||
|
|
||||||
$relayState = $this->request->query('RelayState', '');
|
$relayState = $this->request->query('RelayState', '');
|
||||||
|
|
||||||
$url = $this->base->createLogoutRequest(
|
$url = $this->base->createLogoutRequest(
|
||||||
uid: $uid,
|
uid: $uid,
|
||||||
token: $originToken,
|
token: $originToken,
|
||||||
@@ -61,27 +64,28 @@ class Slo
|
|||||||
issuer: $issuer,
|
issuer: $issuer,
|
||||||
relayState: $relayState
|
relayState: $relayState
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->response->redirect($url, RFC7231::FOUND);
|
return $this->response->redirect($url, RFC7231::FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调方式退出单点登录(IDP 通知 SP)
|
* 回调方式退出单点登录(IDP 通知 SP)
|
||||||
*
|
*
|
||||||
* @return \Psr\Http\Message\ResponseInterface Account 只根据 StatusCode 判断, 符合 [200, 300) 即可
|
* @return PsrResponseInterface Account 只根据 StatusCode 判断, 符合 [200, 300) 即可
|
||||||
|
* @throws RedisException
|
||||||
*/
|
*/
|
||||||
public function callback(): PsrResponseInterface
|
public function callback(): PsrResponseInterface
|
||||||
{
|
{
|
||||||
$redis_prefix = config('common.redis.prefix');
|
$redis_prefix = config('common.redis.prefix');
|
||||||
|
|
||||||
$uid = $this->request->query('uid');
|
$uid = $this->request->query('uid');
|
||||||
$originToken = $this->request->query('token');
|
$originToken = $this->request->query('token');
|
||||||
|
|
||||||
if (empty($originToken)) {
|
if (empty($originToken)) {
|
||||||
$this->authentication->invalidByUser($uid);
|
$this->authentication->invalidByUser($uid);
|
||||||
} else {
|
} else {
|
||||||
$key = "{$redis_prefix}user:token_map:$uid";
|
$key = "{$redis_prefix}user:token_map:$uid";
|
||||||
|
|
||||||
$token = $this->redis->hGet($key, $originToken);
|
$token = $this->redis->hGet($key, $originToken);
|
||||||
$this->redis->hDel($key, $originToken);
|
$this->redis->hDel($key, $originToken);
|
||||||
if ($token) {
|
if ($token) {
|
||||||
@@ -90,4 +94,4 @@ class Slo
|
|||||||
}
|
}
|
||||||
return $this->response->raw('')->withStatus(RFC7231::NO_CONTENT);
|
return $this->response->raw('')->withStatus(RFC7231::NO_CONTENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Singularity\HyperfSaml\Services\Sp;
|
namespace Singularity\HyperfSaml\Services\Sp;
|
||||||
|
|
||||||
use Hyperf\Utils\Codec\Json;
|
use Hyperf\Codec\Json;
|
||||||
use Singularity\HDK\Core\Constants\CommonErrorCode;
|
use Singularity\HDK\Core\Constants\CommonErrorCode;
|
||||||
use Singularity\HDK\Core\Exceptions\Forbidden;
|
use Singularity\HDK\Core\Exceptions\Forbidden;
|
||||||
use Swoole\Exception;
|
use Swoole\Exception;
|
||||||
@@ -24,6 +24,8 @@ use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
|||||||
use Singularity\HyperfSaml\Services\Base;
|
use Singularity\HyperfSaml\Services\Base;
|
||||||
use Teapot\StatusCode\RFC\RFC7231;
|
use Teapot\StatusCode\RFC\RFC7231;
|
||||||
|
|
||||||
|
use function Hyperf\Config\config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SP 单点登录
|
* SP 单点登录
|
||||||
* Singularity\HyperfSaml\Sp\Sso@HyperfSaml
|
* Singularity\HyperfSaml\Sp\Sso@HyperfSaml
|
||||||
@@ -38,7 +40,7 @@ class Sso
|
|||||||
private string $idpAssertionUrl;
|
private string $idpAssertionUrl;
|
||||||
private string $acsUrl;
|
private string $acsUrl;
|
||||||
private string $issuer;
|
private string $issuer;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Base $base,
|
private Base $base,
|
||||||
private RequestInterface $request,
|
private RequestInterface $request,
|
||||||
@@ -48,11 +50,11 @@ class Sso
|
|||||||
) {
|
) {
|
||||||
$this->idpId = config('saml.server.idp_id');
|
$this->idpId = config('saml.server.idp_id');
|
||||||
$this->idpAssertionUrl = config('saml.server.idp_assertion_url');
|
$this->idpAssertionUrl = config('saml.server.idp_assertion_url');
|
||||||
|
|
||||||
$this->issuer = config('saml.client.entity_id');
|
$this->issuer = config('saml.client.entity_id');
|
||||||
$this->acsUrl = config('saml.client.acs_url');
|
$this->acsUrl = config('saml.client.acs_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动重定向获取用户登录状态
|
* 自动重定向获取用户登录状态
|
||||||
*
|
*
|
||||||
@@ -67,7 +69,7 @@ class Sso
|
|||||||
);
|
);
|
||||||
return $this->response->redirect($url, RFC7231::FOUND);
|
return $this->response->redirect($url, RFC7231::FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动重定向到单点登录
|
* 自动重定向到单点登录
|
||||||
*
|
*
|
||||||
@@ -77,7 +79,7 @@ class Sso
|
|||||||
{
|
{
|
||||||
$language = $this->request->query('language') ?? config('language') ?? 'zh_CN';
|
$language = $this->request->query('language') ?? config('language') ?? 'zh_CN';
|
||||||
$relayState = $this->request->query('RelayState', '');
|
$relayState = $this->request->query('RelayState', '');
|
||||||
|
|
||||||
$url = $this->base->createSamlRequest(
|
$url = $this->base->createSamlRequest(
|
||||||
idpID: $this->idpId,
|
idpID: $this->idpId,
|
||||||
acsUrl: $this->acsUrl,
|
acsUrl: $this->acsUrl,
|
||||||
@@ -89,7 +91,7 @@ class Sso
|
|||||||
);
|
);
|
||||||
return $this->response->redirect($url, RFC7231::FOUND);
|
return $this->response->redirect($url, RFC7231::FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证用户同时在多个设备登录
|
* 验证用户同时在多个设备登录
|
||||||
*
|
*
|
||||||
@@ -104,7 +106,7 @@ class Sso
|
|||||||
public function checkMultiDeviceOnline($uid, $token): void
|
public function checkMultiDeviceOnline($uid, $token): void
|
||||||
{
|
{
|
||||||
$allow_multi_online = config('saml.allow_multi_online');
|
$allow_multi_online = config('saml.allow_multi_online');
|
||||||
|
|
||||||
// 只对限制为单设备登录的业务进行验证
|
// 只对限制为单设备登录的业务进行验证
|
||||||
if ($allow_multi_online) {
|
if ($allow_multi_online) {
|
||||||
return;
|
return;
|
||||||
@@ -112,7 +114,7 @@ class Sso
|
|||||||
$redis_prefix = config('common.redis.prefix');
|
$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);
|
$type = $this->redis->type($key);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->stdoutLogger->info('user_token redis type: ' . $type);
|
$this->stdoutLogger->info('user_token redis type: ' . $type);
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
@@ -131,7 +133,7 @@ class Sso
|
|||||||
$this->stdoutLogger->info('user_token redis key: ' . $origin_token);
|
$this->stdoutLogger->info('user_token redis key: ' . $origin_token);
|
||||||
$latest_token = $this->redis->hGet($key, $origin_token);
|
$latest_token = $this->redis->hGet($key, $origin_token);
|
||||||
$this->stdoutLogger->info('user_token redis value isset: ' . $latest_token);
|
$this->stdoutLogger->info('user_token redis value isset: ' . $latest_token);
|
||||||
|
|
||||||
if (empty($latest_token) || $latest_token !== $token) {
|
if (empty($latest_token) || $latest_token !== $token) {
|
||||||
throw new Exception(code: CommonErrorCode::AUTH_SESSION_CREATED_AT_ERROR);
|
throw new Exception(code: CommonErrorCode::AUTH_SESSION_CREATED_AT_ERROR);
|
||||||
}
|
}
|
||||||
@@ -144,8 +146,8 @@ class Sso
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$code = $e->getCode();
|
$code = $e->getCode();
|
||||||
$message = CommonErrorCode::getMessage($code);
|
$message = CommonErrorCode::getMessage($code);
|
||||||
|
|
||||||
throw new Forbidden(code: $code, message: $message);
|
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
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
/**
|
|
||||||
* This file is part of Hyperf.
|
use Hyperf\Context\ApplicationContext;
|
||||||
*
|
use Hyperf\Di\ClassLoader;
|
||||||
* @link https://www.hyperf.io
|
use Hyperf\Di\Container;
|
||||||
* @document https://hyperf.wiki
|
use Hyperf\Di\Definition\DefinitionSource;
|
||||||
* @contact group@hyperf.io
|
use Psr\Container\ContainerInterface;
|
||||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
use Swoole\Runtime;
|
||||||
*/
|
|
||||||
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
|
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