From 020612357a669fec8f49cb302dac27d9f33f30ed Mon Sep 17 00:00:00 2001 From: XueSi <1592328848@qq.com> Date: Thu, 23 Sep 2021 21:10:36 +0800 Subject: [PATCH] fix:the issue #148,#147,#161 --- src/OpenPlatform/Application.php | 22 ++- src/Work/Server/Guard.php | 156 ++++++++++++++++- src/Work/Server/Handlers/EchoStrHandler.php | 28 ++- src/Work/Server/ServiceProvider.php | 2 +- tests/Mock/Message/ServerRequest.php | 13 +- tests/OpenPlatform/ApplicationTest.php | 23 +++ tests/OpenPlatform/Util/MockCache.php | 145 ++++++++++++++++ tests/OpenPlatform/Util/MockLogger.php | 145 ++++++++++++++++ tests/Work/Server/GuardTest.php | 164 ++++++++---------- .../Server/Handlers/EchoStrHandlerTest.php | 54 ++++-- .../Server/Handlers/mock_data/testHandle.json | 9 + .../Server/mock_data/clear_mode_request.json | 9 - .../mock_data/echostr_validate_request.json | 9 + .../Server/mock_data/message_request.json | 8 + .../Server/mock_data/safe_mode_request.json | 12 -- 15 files changed, 649 insertions(+), 150 deletions(-) create mode 100644 tests/OpenPlatform/Util/MockCache.php create mode 100644 tests/OpenPlatform/Util/MockLogger.php create mode 100644 tests/Work/Server/Handlers/mock_data/testHandle.json delete mode 100644 tests/Work/Server/mock_data/clear_mode_request.json create mode 100644 tests/Work/Server/mock_data/echostr_validate_request.json create mode 100644 tests/Work/Server/mock_data/message_request.json delete mode 100644 tests/Work/Server/mock_data/safe_mode_request.json diff --git a/src/OpenPlatform/Application.php b/src/OpenPlatform/Application.php index 3c880cc..c1f99b0 100644 --- a/src/OpenPlatform/Application.php +++ b/src/OpenPlatform/Application.php @@ -64,7 +64,7 @@ class Application extends ServiceContainer * @param AccessToken|null $accessToken * @return OfficialAccount */ - public function officialAccount(string $appId, string $refreshToken = null, AccessToken $accessToken = null, $reUseLoggerCache = false): OfficialAccount + public function officialAccount(string $appId, string $refreshToken = null, AccessToken $accessToken = null): OfficialAccount { $officialAccount = new OfficialAccount($this->getAuthorizerConfig($appId, $refreshToken), null, [ ServiceProviders::AccessToken => $accessToken ?: function ($app) { @@ -84,11 +84,10 @@ public function officialAccount(string $appId, string $refreshToken = null, Acce } ]); - if ($reUseLoggerCache) { - foreach ([ServiceProviders::Cache, ServiceProviders::Logger] as $reuse) { - if (isset($this[$reuse])) { - $officialAccount[$reuse] = $this[$reuse]; - } + // 复用开放平台的logger cache为授权公众号提供服务 + foreach ([ServiceProviders::Cache, ServiceProviders::Logger] as $reuse) { + if (isset($this[$reuse])) { + $officialAccount[$reuse] = $this[$reuse]; } } @@ -104,7 +103,7 @@ public function officialAccount(string $appId, string $refreshToken = null, Acce * @param AccessToken|null $accessToken * @return MiniProgram */ - public function miniProgram(string $appId, string $refreshToken = null, AccessToken $accessToken = null, $reUseLoggerCache = false): MiniProgram + public function miniProgram(string $appId, string $refreshToken = null, AccessToken $accessToken = null): MiniProgram { $miniProgram = new MiniProgram($this->getAuthorizerConfig($appId, $refreshToken), null, [ ServiceProviders::AccessToken => $accessToken ?: function ($app) { @@ -121,11 +120,10 @@ public function miniProgram(string $appId, string $refreshToken = null, AccessTo } ]); - if ($reUseLoggerCache) { - foreach ([ServiceProviders::Cache, ServiceProviders::Logger] as $reuse) { - if (isset($this[$reuse])) { - $miniProgram[$reuse] = $this[$reuse]; - } + // 复用开放平台的logger cache为授权小程序提供服务 + foreach ([ServiceProviders::Cache, ServiceProviders::Logger] as $reuse) { + if (isset($this[$reuse])) { + $miniProgram[$reuse] = $this[$reuse]; } } diff --git a/src/Work/Server/Guard.php b/src/Work/Server/Guard.php index 826bafa..9c644ce 100644 --- a/src/Work/Server/Guard.php +++ b/src/Work/Server/Guard.php @@ -2,10 +2,23 @@ namespace EasySwoole\WeChat\Work\Server; +use EasySwoole\WeChat\Kernel\Contracts\MessageInterface; use EasySwoole\WeChat\Kernel\Contracts\RequestMessageInterface; +use EasySwoole\WeChat\Kernel\Exceptions\BadRequestException; +use EasySwoole\WeChat\Kernel\Exceptions\InvalidArgumentException; +use EasySwoole\WeChat\Kernel\Exceptions\RuntimeException; +use EasySwoole\WeChat\Kernel\Messages\News; +use EasySwoole\WeChat\Kernel\Messages\NewsItem; +use EasySwoole\WeChat\Kernel\Messages\Raw; +use EasySwoole\WeChat\Kernel\Messages\Text; +use EasySwoole\WeChat\Kernel\Psr\Response; use EasySwoole\WeChat\Kernel\ServerGuard; +use EasySwoole\WeChat\Kernel\ServiceProviders; +use EasySwoole\WeChat\Kernel\Utility\XML; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use EasySwoole\WeChat\Kernel\Messages\Message; +use Throwable; /** * Class Guard @@ -24,6 +37,147 @@ public function isValidateRequest(ServerRequestInterface $request): bool return isset($request->getQueryParams()['echostr']); } + /** + * @param ServerRequestInterface $request + * @return bool + */ + public function isSafeMode(ServerRequestInterface $request): bool + { + return true; + } + + /** + * @param ServerRequestInterface $request + * @return $this + * @throws BadRequestException + */ + public function validate(ServerRequestInterface $request): ServerGuard + { + if (!$this->alwaysValidate && !$this->isSafeMode($request)) { + return $this; + } + + if (empty($request->getQueryParams()['msg_signature']) + || empty($request->getQueryParams()['timestamp']) + || empty($request->getQueryParams()['nonce']) + ) { + throw new BadRequestException('Invalid request params.', 400); + } + + if (!$this->isValidateRequest($request)) { + $postContent = $request->getBody()->getContents(); + if (0 === stripos($postContent, '<')) { + $postContent = XML::parse($postContent); + } else { + throw new BadRequestException('Invalid request body.', 400); + } + + if (empty($postContent) || !is_array($postContent)) { + throw new BadRequestException('No message received.'); + } + + $makeSignature = $this->signature( + $this->getToken(), + $request->getQueryParams()['timestamp'] ?? "", + $request->getQueryParams()['nonce'] ?? "", + $postContent['Encrypt'] + ); + } else { + // echostr 签名校验 + $makeSignature = $this->signature( + $this->getToken(), + $request->getQueryParams()['timestamp'] ?? "", + $request->getQueryParams()['nonce'] ?? "", + $request->getQueryParams()['echostr'] + ); + } + + if (($request->getQueryParams()['msg_signature'] ?? "") !== $makeSignature) { + throw new BadRequestException('Invalid request signature.', 400); + } + + return $this; + } + + /** + * @param ServerRequestInterface $request + * @return ResponseInterface + * @throws Throwable + */ + protected function resolve(ServerRequestInterface $request): ResponseInterface + { + if ($this->isValidateRequest($request)) { + $replyMessage = $this->dispatch(Message::VALIDATE, $request); + } else { + $requestMessage = $this->parseRequest($request); + $replyMessage = $this->dispatch($requestMessage->getType(), $requestMessage); + } + + /** + * "", [] + */ + if ((empty($replyMessage) && ($replyMessage !== 0 && $replyMessage !== "0"))) { + return new Response( + 200, + ['Content-Type' => 'application/text'], + '' + ); + } + + if ($replyMessage instanceof Raw) { + $replyString = $replyMessage->__toString(); + } else { + if (is_string($replyMessage) || is_numeric($replyMessage)) { + $replyMessage = new Text($replyMessage); + } elseif (is_array($replyMessage) && reset($replyMessage) instanceof NewsItem) { + $replyMessage = new News($replyMessage); + } + + if (!$replyMessage instanceof MessageInterface) { + throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($replyMessage))); + } + + /** 除非 Message::VALIDATE 出了问题 否则不会走走这里 */ + if (!isset($requestMessage)) { + throw new RuntimeException("Invalid request message."); + } + $replyString = $this->buildReply($requestMessage->getFromUserName(), $requestMessage->getToUserName(), $replyMessage); + } + + if ($this->isSafeMode($request) && !$this->isValidateRequest($request)) { + $encrypted = $this->app[ServiceProviders::Encryptor]->encrypt( + $replyString, + $this->app[ServiceProviders::Config]->get("aesKey"), + $this->app[ServiceProviders::Config]->get("corpId") + ); + $replyString = $this->buildEncryptedReply($encrypted); + } + + return new Response( + 200, + ['Content-Type' => 'application/xml'], + $replyString + ); + } + + + /** + * @param array $message + * @return string|null + */ + protected function decryptMessage(array $message): ?string + { + $configs = $this->app->getConfig(); + $appId = $configs['corpId']; + $aesKey = $configs['aesKey']; + + return $this->app[ServiceProviders::Encryptor]->decrypt( + $message['Encrypt'], + $aesKey, + $appId + ); + } + /** * @param array $message * @return RequestMessageInterface @@ -63,4 +217,4 @@ public function toXmlArray(): array } }; } -} \ No newline at end of file +} diff --git a/src/Work/Server/Handlers/EchoStrHandler.php b/src/Work/Server/Handlers/EchoStrHandler.php index fd2a516..01f6899 100644 --- a/src/Work/Server/Handlers/EchoStrHandler.php +++ b/src/Work/Server/Handlers/EchoStrHandler.php @@ -3,7 +3,10 @@ namespace EasySwoole\WeChat\Work\Server\Handlers; use EasySwoole\WeChat\Kernel\Contracts\EventHandlerInterface; +use EasySwoole\WeChat\Kernel\Encryptor; use EasySwoole\WeChat\Kernel\Messages\Raw; +use EasySwoole\WeChat\Kernel\ServiceProviders; +use Pimple\Container; use Psr\Http\Message\ServerRequestInterface; /** @@ -14,10 +17,31 @@ */ class EchoStrHandler implements EventHandlerInterface { + /** + * @var Container + */ + protected $app; + + /** + * EchoStrHandler constructor. + * @param Container $app + */ + public function __construct(Container $app) + { + $this->app = $app; + } + public function handle($request = null) { if ($request instanceof ServerRequestInterface) { - $str = $request->getQueryParams()['echostr'] ?? null; + $echoStr = $request->getQueryParams()['echostr'] ?? null; + /** @var Encryptor $encryptor */ + $encryptor = $this->app[ServiceProviders::Encryptor]; + $str = $encryptor->decrypt( + $echoStr, + $this->app[ServiceProviders::Config]->get('aesKey'), + $this->app[ServiceProviders::Config]->get('corpId') + ); if (!is_null($str)) { return new Raw($str); } @@ -25,4 +49,4 @@ public function handle($request = null) return null; } -} \ No newline at end of file +} diff --git a/src/Work/Server/ServiceProvider.php b/src/Work/Server/ServiceProvider.php index 8eb815d..98d966c 100644 --- a/src/Work/Server/ServiceProvider.php +++ b/src/Work/Server/ServiceProvider.php @@ -30,7 +30,7 @@ public function register(Container $app) if (!isset($app[Application::Server])) { $app[Application::Server] = function (ServiceContainer $app) { $guard = new Guard($app); - $guard->push(new EchoStrHandler(), Message::VALIDATE); + $guard->push(new EchoStrHandler($app), Message::VALIDATE); return $guard; }; } diff --git a/tests/Mock/Message/ServerRequest.php b/tests/Mock/Message/ServerRequest.php index 0a99fda..0ea6c47 100644 --- a/tests/Mock/Message/ServerRequest.php +++ b/tests/Mock/Message/ServerRequest.php @@ -21,8 +21,15 @@ public function __construct($method = 'GET', Uri $uri = null, array $headers = n $query = []; foreach (explode("&", $this->getUri()->getQuery()) as $group) { if (!empty($group)) { - $group = explode("=", $group); - $query[$group[0]] = $group[1]; + $groupArr = explode("=", $group); + + // 针对 value 有 = 符号的做兼容 + if ($groupArr > 2) { + $firstPos = strpos($group, '='); + $query[$groupArr[0]] = substr($group, $firstPos + 1); + } else { + $query[$groupArr[0]] = $groupArr[1]; + } } } $this->withQueryParams($query); @@ -146,4 +153,4 @@ public function withoutAttribute($name) unset($this->attributes[$name]); return $this; } -} \ No newline at end of file +} diff --git a/tests/OpenPlatform/ApplicationTest.php b/tests/OpenPlatform/ApplicationTest.php index 2dfab72..69c5e5b 100644 --- a/tests/OpenPlatform/ApplicationTest.php +++ b/tests/OpenPlatform/ApplicationTest.php @@ -6,6 +6,8 @@ use EasySwoole\WeChat\OpenPlatform\Application; use EasySwoole\WeChat\OpenPlatform\Authorizer\OfficialAccount\Application as OfficialAccount; use EasySwoole\WeChat\Tests\Mock\Message\Status; +use EasySwoole\WeChat\Tests\OpenPlatform\Util\MockCache; +use EasySwoole\WeChat\Tests\OpenPlatform\Util\MockLogger; use EasySwoole\WeChat\Tests\TestCase; use Psr\Http\Message\ServerRequestInterface; use EasySwoole\WeChat\OpenPlatform\Base; @@ -209,6 +211,27 @@ public function testDynamicCalls2() $this->assertSame(json_decode($this->readMockResponseJson('createPreAuthorizationCode.json'), true), $client->createPreAuthorizationCode()); } + // 保证开放平台授权的应用和开放平台原本的 logger 和 cache 保持一致 + public function testLogger() + { + $app = new Application([ + 'appId' => 'component-app-id', + 'appSecret' => 'component-secret', + 'token' => 'component-token', + 'aesKey' => 'Qqx2S6jV3mp5prWPg5x3eBmeU1kLayZio4Q9ZxWTbmf']); + + $app->rebind(ServiceProviders::Logger, new MockLogger()); + $app->rebind(ServiceProviders::Cache, new MockCache()); + + $miniProgram = $app->miniProgram('mock_miniProgram_app_id', 'mock_miniProgram_refresh-token'); + $this->assertSame($app->logger, $miniProgram->logger); + $this->assertSame($app->cache, $miniProgram->cache); + + $officialAccount = $app->officialAccount('mock_officialAccount_app_id', 'mock_officialAccount_refresh-token'); + $this->assertSame($app->logger, $officialAccount->logger); + $this->assertSame($app->cache, $officialAccount->cache); + } + protected function readMockResponseJson(string $file): string { return file_get_contents(dirname(__FILE__) . '/Base/mock_data/' . $file); diff --git a/tests/OpenPlatform/Util/MockCache.php b/tests/OpenPlatform/Util/MockCache.php new file mode 100644 index 0000000..8188b59 --- /dev/null +++ b/tests/OpenPlatform/Util/MockCache.php @@ -0,0 +1,145 @@ + + */ + +namespace EasySwoole\WeChat\Tests\OpenPlatform\Util; + +use Psr\SimpleCache\CacheInterface; + +class MockCache implements CacheInterface +{ + /** + * Fetches a value from the cache. + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return mixed The value of the item from the cache, or $default in case of cache miss. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function get($key, $default = null) + { + // TODO: Implement get() method. + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * @param string $key The key of the item to store. + * @param mixed $value The value of the item to store, must be serializable. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function set($key, $value, $ttl = null) + { + // TODO: Implement set() method. + } + + /** + * Delete an item from the cache by its unique key. + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function delete($key) + { + // TODO: Implement delete() method. + } + + /** + * Wipes clean the entire cache's keys. + * + * @return bool True on success and false on failure. + */ + public function clear() + { + // TODO: Implement clear() method. + } + + /** + * Obtains multiple cache items by their unique keys. + * + * @param iterable $keys A list of keys that can obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * + * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null) + { + // TODO: Implement getMultiple() method. + } + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null) + { + // TODO: Implement setMultiple() method. + } + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys) + { + // TODO: Implement deleteMultiple() method. + } + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key) + { + // TODO: Implement has() method. + } +} diff --git a/tests/OpenPlatform/Util/MockLogger.php b/tests/OpenPlatform/Util/MockLogger.php new file mode 100644 index 0000000..a55abf3 --- /dev/null +++ b/tests/OpenPlatform/Util/MockLogger.php @@ -0,0 +1,145 @@ + + */ + +namespace EasySwoole\WeChat\Tests\OpenPlatform\Util; + +use Psr\Log\LoggerInterface; + +class MockLogger implements LoggerInterface +{ + + /** + * System is unusable. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function emergency($message, array $context = array()) + { + // TODO: Implement emergency() method. + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + // TODO: Implement alert() method. + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + // TODO: Implement critical() method. + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + // TODO: Implement error() method. + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + // TODO: Implement warning() method. + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + // TODO: Implement notice() method. + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + // TODO: Implement info() method. + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + // TODO: Implement debug() method. + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param mixed[] $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // TODO: Implement log() method. + } +} diff --git a/tests/Work/Server/GuardTest.php b/tests/Work/Server/GuardTest.php index cdc8fc2..f681f88 100644 --- a/tests/Work/Server/GuardTest.php +++ b/tests/Work/Server/GuardTest.php @@ -9,168 +9,144 @@ namespace EasySwoole\WeChat\Tests\Work\Server; - -use EasySwoole\WeChat\Kernel\Contracts\RequestMessageInterface; +use EasySwoole\WeChat\Kernel\Contracts\MessageInterface; +use EasySwoole\WeChat\Kernel\Encryptor; use EasySwoole\WeChat\Kernel\Exceptions\BadRequestException; +use EasySwoole\WeChat\Kernel\Messages\Message; +use EasySwoole\WeChat\Kernel\Messages\Text; use EasySwoole\WeChat\Kernel\ServiceContainer; -use EasySwoole\WeChat\Kernel\Utility\XML; +use EasySwoole\WeChat\Kernel\ServiceProviders; use EasySwoole\WeChat\Tests\Mock\Message\Status; use EasySwoole\WeChat\Tests\TestCase; use EasySwoole\WeChat\Work\Server\Guard; -use EasySwoole\WeChat\Work\Server\ServiceProvider; +use EasySwoole\WeChat\Work\Server\Handlers\EchoStrHandler; use Psr\Http\Message\ResponseInterface; use ReflectionProperty; class GuardTest extends TestCase { /** - * @throws \EasySwoole\WeChat\Kernel\Exceptions\BadRequestException + * 【验证回调URL】 + * 企业开启回调模式时,企业号会向验证url发送一个get请求 + * 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + * + * @throws BadRequestException + * @throws \EasySwoole\WeChat\Kernel\Exceptions\InvalidArgumentException * @throws \Throwable */ - public function testServer() + public function testServerForValidateCallbackUrl() { - $mockData = $this->readMockData('clear_mode_request.json'); + $mockData = $this->readMockData('echostr_validate_request.json'); $mockData = json_decode($mockData, true); $mockRequest = $this->buildRequest( - "POST", - "https://test.com?" . http_build_query($mockData['query']), + "GET", + "https://qy.weixin.qq.com/cgi-bin/wxpush?" . urldecode(http_build_query($mockData['query'])), [], $mockData['body'] ); $app = new ServiceContainer([ - 'corpId' => 'mock_corpId', + 'corpId' => 'wx5823bf96d3bd56c7', 'corpSecret' => 'mock_corpSecret', - 'token' => 'mock_token' + 'token' => 'QDG6eK', + 'aesKey' => 'jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C', ]); + + $app->rebind(ServiceProviders::Encryptor, new Encryptor()); + $guard = new Guard($app); + $guard->push(new EchoStrHandler($app), Message::VALIDATE); + $response = $guard->serve($mockRequest); $this->assertInstanceOf(ResponseInterface::class, $response); $this->assertSame(Status::CODE_OK, $response->getStatusCode()); - $this->assertSame(Guard::SUCCESS_EMPTY_RESPONSE, $response->getBody()->__toString()); - $this->assertSame(['Content-Type' => ['application/text']], $response->getHeaders()); + $this->assertSame('1616140317555161061', $response->getBody()->__toString()); + $this->assertSame(['Content-Type' => ['application/xml']], $response->getHeaders()); } /** - * @throws \ReflectionException + * 【对用户回复的消息解密、企业回复用户消息的加密】 + * + * @throws BadRequestException + * @throws \EasySwoole\WeChat\Kernel\Exceptions\InvalidArgumentException + * @throws \Throwable */ - public function testForceValidate() - { - $app = new ServiceContainer([ - 'corpId' => 'mock_corpId', - 'corpSecret' => 'mock_corpSecret', - 'token' => 'mock_token' - ]); - $guard = new Guard($app); - - $reflectionProperty = new ReflectionProperty($guard, 'alwaysValidate'); - $reflectionProperty->setAccessible(true); - $this->assertFalse($reflectionProperty->getValue($guard)); - - $guard->forceValidate(); - $this->assertTrue($reflectionProperty->getValue($guard)); - } - - public function testIsSafeMode() + public function testServerForMessageRequest() { + $mockData = $this->readMockData('message_request.json'); + $mockData = json_decode($mockData, true); $mockRequest = $this->buildRequest( - "POST", - "https://test.com?signature=xxx&encrypt_type=aes" + "GET", + "https://qy.weixin.qq.com/cgi-bin/wxpush?" . urldecode(http_build_query($mockData['query'])), + [], + $mockData['body'] ); $app = new ServiceContainer([ - 'corpId' => 'mock_corpId', + 'corpId' => 'wx5823bf96d3bd56c7', 'corpSecret' => 'mock_corpSecret', - 'token' => 'mock_token' + 'token' => 'QDG6eK', + 'aesKey' => 'jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C', ]); - $guard = new Guard($app); - $this->assertTrue($guard->isSafeMode($mockRequest)); - $mockRequest = $this->buildRequest( - "POST", - "https://test.com?signature=xxx" - ); - $this->assertFalse($guard->isSafeMode($mockRequest)); - } - - public function testIsValidateRequest() - { - $mockRequest = $this->buildRequest( - "POST", - "https://test.com?echostr=mock-echostr" - ); + $app->rebind(ServiceProviders::Encryptor, new Encryptor()); - $app = new ServiceContainer([ - 'corpId' => 'mock_corpId', - 'corpSecret' => 'mock_corpSecret', - 'token' => 'mock_token' - ]); $guard = new Guard($app); - $this->assertTrue($guard->isValidateRequest($mockRequest)); - $mockRequest = $this->buildRequest( - "POST", - "https://test.com?signature=xxx" - ); - $this->assertFalse($guard->isValidateRequest($mockRequest)); + $guard->push(function (MessageInterface $message) { + return new Text('Hello EasySwooleWeChat!'); + }, Message::TEXT); + + $response = $guard->serve($mockRequest); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertSame(Status::CODE_OK, $response->getStatusCode()); + $this->assertIsString($response->getBody()->__toString()); + $this->assertSame(['Content-Type' => ['application/xml']], $response->getHeaders()); } /** - * @throws BadRequestException + * @throws \ReflectionException */ - public function testParseRequest() + public function testForceValidate() { - $mockData = $this->readMockData('clear_mode_request.json'); - $mockData = json_decode($mockData, true); - $mockRequest = $this->buildRequest( - "POST", - "https://test.com?" . http_build_query($mockData['query']), - [], - $mockData['body'] - ); - $app = new ServiceContainer([ 'corpId' => 'mock_corpId', 'corpSecret' => 'mock_corpSecret', - 'token' => 'dczmnau31ea9nzcn13i91uedjdal' + 'token' => 'mock_token' ]); + $guard = new Guard($app); - $this->assertInstanceOf(RequestMessageInterface::class, $guard->parseRequest($mockRequest)); + $reflectionProperty = new ReflectionProperty($guard, 'alwaysValidate'); + $reflectionProperty->setAccessible(true); + + $this->assertFalse($reflectionProperty->getValue($guard)); + + $guard->forceValidate(); - $this->assertEquals($mockData['body'], $guard->parseRequest($mockRequest)->transformToXml()); - $this->assertEquals(XML::parse($mockData['body']), $guard->parseRequest($mockRequest)->transformForJsonRequest()); + $this->assertTrue($reflectionProperty->getValue($guard)); } - /** - * @throws BadRequestException - */ - public function testParseRequestWithSafeMode() + public function testIsSafeMode() { - $mockData = $this->readMockData('safe_mode_request.json'); - $mockData = json_decode($mockData, true); $mockRequest = $this->buildRequest( "POST", - "https://test.com?" . http_build_query($mockData['query']), - [], - $mockData['body'] + "https://qy.weixin.qq.com?msg_signature=ASDFQWEXZCVAQFASDFASDFSS×tamp=13500001234&nonce=123412323" ); $app = new ServiceContainer([ 'corpId' => 'mock_corpId', 'corpSecret' => 'mock_corpSecret', - 'token' => 'dczmnau31ea9nzcn13i91uedjdal', - 'aesKey' => 'pz2Zl2by0oZQ6AUV4EvnbF7A7VRUvrLN1vqnxsUjmTW' + 'token' => 'mock_token' ]); - $app->register(new ServiceProvider()); $guard = new Guard($app); - $this->assertInstanceOf(RequestMessageInterface::class, $guard->parseRequest($mockRequest)); - - $this->assertEquals($mockData['clear_body'], $guard->parseRequest($mockRequest)->transformToXml()); - $this->assertEquals(XML::parse($mockData['clear_body']), $guard->parseRequest($mockRequest)->transformForJsonRequest()); + $this->assertTrue($guard->isSafeMode($mockRequest)); } /** @@ -181,4 +157,4 @@ private function readMockData(string $filename): string { return file_get_contents(__DIR__ . '/mock_data/' . $filename); } -} \ No newline at end of file +} diff --git a/tests/Work/Server/Handlers/EchoStrHandlerTest.php b/tests/Work/Server/Handlers/EchoStrHandlerTest.php index 6b5cb5d..4bcbcb7 100644 --- a/tests/Work/Server/Handlers/EchoStrHandlerTest.php +++ b/tests/Work/Server/Handlers/EchoStrHandlerTest.php @@ -10,7 +10,10 @@ namespace EasySwoole\WeChat\Tests\Work\Server\Handlers; +use EasySwoole\WeChat\Kernel\Encryptor; use EasySwoole\WeChat\Kernel\Messages\Raw; +use EasySwoole\WeChat\Kernel\ServiceContainer; +use EasySwoole\WeChat\Kernel\ServiceProviders; use EasySwoole\WeChat\Tests\Mock\Message\Request; use EasySwoole\WeChat\Tests\TestCase; use EasySwoole\WeChat\Work\Server\Handlers\EchoStrHandler; @@ -19,34 +22,53 @@ class EchoStrHandlerTest extends TestCase { public function testHandle() { + $mockData = $this->readMockData('testHandle.json'); + $mockData = json_decode($mockData, true); $mockRequest = $this->buildRequest( - "POST", - "https://test.com?echostr=mock-echostr" + "GET", + "https://qy.weixin.qq.com/cgi-bin/wxpush?" . urldecode(http_build_query($mockData['query'])), + [], + $mockData['body'] ); - $handle = new EchoStrHandler(); + $app = new ServiceContainer([ + 'corpId' => 'wx5823bf96d3bd56c7', + 'corpSecret' => 'mock_corpSecret', + 'token' => 'QDG6eK', + 'aesKey' => 'jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C', + ]); + + $app->rebind(ServiceProviders::Encryptor, new Encryptor()); + + $handle = new EchoStrHandler($app); $result = $handle->handle($mockRequest); $this->assertInstanceOf(Raw::class, $result); - $this->assertSame("mock-echostr", $result->getContent()); + $this->assertSame("1616140317555161061", $result->getContent()); } - public function testHandleWithoutEchoStr() + public function testHandleInvalidRequest() { - $mockRequest = $this->buildRequest( - "POST", - "https://test.com?signature=xxx" - ); + $app = new ServiceContainer([ + 'corpId' => 'wx5823bf96d3bd56c7', + 'corpSecret' => 'mock_corpSecret', + 'token' => 'QDG6eK', + 'aesKey' => 'jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C', + ]); - $handle = new EchoStrHandler(); + $app->rebind(ServiceProviders::Encryptor, new Encryptor()); - $this->assertSame(null, $handle->handle($mockRequest)); + $handle = new EchoStrHandler($app); + + $this->assertSame(null, $handle->handle(new Request())); } - public function testHandleInvalidRequest() + /** + * @param string $filename + * @return string + */ + private function readMockData(string $filename): string { - $handle = new EchoStrHandler(); - - $this->assertSame(null, $handle->handle(new Request())); + return file_get_contents(__DIR__ . '/mock_data/' . $filename); } -} \ No newline at end of file +} diff --git a/tests/Work/Server/Handlers/mock_data/testHandle.json b/tests/Work/Server/Handlers/mock_data/testHandle.json new file mode 100644 index 0000000..8fc3eb1 --- /dev/null +++ b/tests/Work/Server/Handlers/mock_data/testHandle.json @@ -0,0 +1,9 @@ +{ + "query": { + "msg_signature": "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3", + "timestamp": "1409659589", + "nonce": "263014780", + "echostr": "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" + }, + "body": "" +} diff --git a/tests/Work/Server/mock_data/clear_mode_request.json b/tests/Work/Server/mock_data/clear_mode_request.json deleted file mode 100644 index d737224..0000000 --- a/tests/Work/Server/mock_data/clear_mode_request.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "query": { - "signature": "34a079a3581b6099afebf59cfc1f570ee488f9d0", - "timestamp": "1606128379", - "nonce": "1095164962", - "openid": "o1YYd0mJxkRO0eCi7haWLDVMalts" - }, - "body": "160612837922994230328393770" -} \ No newline at end of file diff --git a/tests/Work/Server/mock_data/echostr_validate_request.json b/tests/Work/Server/mock_data/echostr_validate_request.json new file mode 100644 index 0000000..8fc3eb1 --- /dev/null +++ b/tests/Work/Server/mock_data/echostr_validate_request.json @@ -0,0 +1,9 @@ +{ + "query": { + "msg_signature": "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3", + "timestamp": "1409659589", + "nonce": "263014780", + "echostr": "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" + }, + "body": "" +} diff --git a/tests/Work/Server/mock_data/message_request.json b/tests/Work/Server/mock_data/message_request.json new file mode 100644 index 0000000..ee37f12 --- /dev/null +++ b/tests/Work/Server/mock_data/message_request.json @@ -0,0 +1,8 @@ +{ + "query": { + "msg_signature": "477715d11cdb4164915debcba66cb864d751f3e6", + "timestamp": "1409659813", + "nonce": "1372623149" + }, + "body": "" +} diff --git a/tests/Work/Server/mock_data/safe_mode_request.json b/tests/Work/Server/mock_data/safe_mode_request.json deleted file mode 100644 index 0ce7b22..0000000 --- a/tests/Work/Server/mock_data/safe_mode_request.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "query": { - "signature":"35c35a17908ebfaf8540ee11f7590764a88df32e", - "timestamp":"1606180982", - "nonce":"535086195", - "openid":"o1YYd0mJxkRO0eCi7haWLDVMalts", - "encrypt_type":"aes", - "msg_signature":"4f972a15c29b10ead13819cf575ed6154cb44715" - }, - "body": "", - "clear_body": "160618098222994982940022977" -} \ No newline at end of file