Skip to content

Commit

Permalink
Optimal SecurityHttp component (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
zds-s authored Apr 20, 2024
1 parent ae5e1f3 commit 294e168
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 1 deletion.
2 changes: 2 additions & 0 deletions publish/security.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/
use Mine\Security\Http\Jwt\Black\CacheBlack;
use Mine\Security\Http\Jwt\Token;
use Mine\Security\Http\UserProvider;
use Mine\SecurityBundle\Context\Context;

Expand All @@ -20,6 +21,7 @@
* user provider class
*/
'provider' => UserProvider::class,
'token' => Token::class,
/*
* entity class
*/
Expand Down
29 changes: 29 additions & 0 deletions src/Aspect/CurrentUserAspect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact [email protected]
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace Mine\Security\Http\Aspect;

use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Mine\Security\Http\Attribute\CurrentUser;

class CurrentUserAspect extends AbstractAspect
{
public array $annotations = [
CurrentUser::class,
];

public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
return $proceedingJoinPoint->process();
}
}
7 changes: 6 additions & 1 deletion src/Attribute/CurrentUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@
use Hyperf\Di\Annotation\AbstractAnnotation;

#[\Attribute(\Attribute::TARGET_PARAMETER)]
class CurrentUser extends AbstractAnnotation {}
class CurrentUser extends AbstractAnnotation
{
public function __construct(
public string $secret = 'default'
) {}
}
75 changes: 75 additions & 0 deletions src/Command/GenJwtSecretCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact [email protected]
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace Mine\Security\Http\Command;

use Hyperf\Command\Annotation\Command;
use Hyperf\Command\Command as Base;
use Hyperf\Stringable\Str;
use Symfony\Component\Console\Input\InputOption;

#[Command]
class GenJwtSecretCommand extends Base
{
protected ?string $name = 'mine:gen-jwt-secret';

public function __invoke()
{
$secretName = $this->getSecretName();
$value = $this->generator();
$envPath = $this->getEnvPath();

if (! file_exists($envPath)) {
$this->error('.env file not is exists!');
return;
}
if (\Mine\Helper\Str::contains(file_get_contents($envPath), $secretName) === false) {
file_put_contents($envPath, "\n{$secretName}={$value}\n", FILE_APPEND);
} else {
file_put_contents($envPath, preg_replace(
"~{$secretName}\\s*=\\s*[^\n]*~",
"{$secretName}=\"{$value}\"",
file_get_contents($envPath)
));
}

$this->info('jwt secret generator successfully:' . $value);
}

public function getSecretName(): string
{
return Str::upper($this->input->getOption('secret-name'));
}

public function getEnvPath(): string
{
return BASE_PATH . '/.env';
}

public function generator(): string
{
return base64_encode(random_bytes(64));
}

protected function configure()
{
$this->setHelp('run "php bin/hyperf.php mine:gen-jwt" create the new jwt secret');
$this->setDescription('MineAdmin system gen jwt command');
}

protected function getOptions(): array
{
return [
'secret-name', 'sn', InputOption::VALUE_OPTIONAL, 'The jwt secret name.default:jwt_secret', 'jwt_secret',
];
}
}
102 changes: 102 additions & 0 deletions src/CurrentUserProxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact [email protected]
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace Mine\Security\Http;

use Hyperf\Context\Traits\CoroutineProxy;
use Hyperf\Database\Model\Builder;
use Mine\SecurityBundle\Contract\UserInterface;
use Mine\SecurityBundle\Security;
use Psr\Container\ContainerInterface;

use function Hyperf\Support\call;

class CurrentUserProxy implements UserInterface
{
use CoroutineProxy;

protected string $proxyKey = 'secret.http.proxy';

private Security $security;

public function __construct(
private string $scene,
private ContainerInterface $container
) {
$this->security = $this->container->get(Security::class);
}

public function __get($name)
{
return $this->getAttributes()[$name] ?? null;
}

public function getEntity(): UserInterface
{
return $this->getSecurity()->getToken()->user($this->scene);
}

public function getIdentifier(): string
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function getIdentifierName(): string
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function getRememberToken(): string
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function setRememberToken(string $token): void
{
call([$this->getEntity(), __FUNCTION__], func_get_args());
}

public function getRememberTokenName(): string
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function getPassword(): string
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function setPassword(string $password): void
{
call([$this->getEntity(), __FUNCTION__]);
}

public function getSecurityBuilder(): Builder
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function setAttribute(string $key, mixed $value)
{
return call([$this->getEntity(), __FUNCTION__]);
}

public function getAttributes(): array
{
return call([$this->getEntity(), __FUNCTION__]);
}

protected function getSecurity(): Security
{
return $this->security;
}
}
43 changes: 43 additions & 0 deletions src/RegisterCurrentUserPropertyHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact [email protected]
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace Mine\Security\Http;

use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Definition\PropertyHandlerManager;
use Hyperf\Di\ReflectionManager;
use Mine\Security\Http\Attribute\CurrentUser;

class RegisterCurrentUserPropertyHandler
{
public static bool $registered = false;

public static function register(): void
{
if (static::$registered) {
return;
}
PropertyHandlerManager::register(CurrentUser::class, [static::class, 'handle']);
}

public static function handle($object, $currentClassName, $targetClassName, $property, $annotation): void
{
if ($annotation instanceof CurrentUser) {
$reflectionProperty = ReflectionManager::reflectProperty($currentClassName, $property);
$container = ApplicationContext::getContainer();
$reflectionProperty->setValue(new CurrentUserProxy(
$annotation->secret,
$container,
));
}
}
}
73 changes: 73 additions & 0 deletions tests/Cases/Command/GenJwtSecretCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);
/**
* This file is part of MineAdmin.
*
* @link https://www.mineadmin.com
* @document https://doc.mineadmin.com
* @contact [email protected]
* @license https://github.com/mineadmin/MineAdmin/blob/master/LICENSE
*/

namespace Mine\Security\Http\Tests\Cases\Command;

use Hyperf\Stringable\Str;
use Mine\Security\Http\Command\GenJwtSecretCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\Input;

/**
* @internal
* @coversNothing
*/
class GenJwtSecretCommandTest extends TestCase
{
public function testGenerator(): void
{
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
$invoke = $reflection->getMethod('generator');
$this->assertIsString($invoke->invoke(\Mockery::mock(GenJwtSecretCommand::class)));
}

public function testGetSecretName(): void
{
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
$invoke = $reflection->getMethod('getSecretName');
$instance = $reflection->newInstanceWithoutConstructor();
$input = \Mockery::mock(Input::class);
$input->allows('getOption')->andReturn('xxx');
$instance->setInput($input);
$this->assertSame('XXX', $invoke->invoke($instance));
}

public function testGetEnvPath(): void
{
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
$invoke = $reflection->getMethod('getEnvPath');
$m = \Mockery::mock(GenJwtSecretCommand::class);
$this->assertSame(BASE_PATH . '/.env', $invoke->invoke($m));
}

public function testInvoke(): void
{
$reflection = new \ReflectionClass(GenJwtSecretCommand::class);
$invoke = $reflection->getMethod('__invoke');
$m = \Mockery::mock(GenJwtSecretCommand::class);
$m->shouldAllowMockingProtectedMethods();
$env = sys_get_temp_dir() . '/' . Str::random(32) . '.env';
file_put_contents($env, "xxx=1\n");
$m->allows('getEnvPath')->andReturn($env);
$input = \Mockery::mock(Input::class);
$input->allows('getOption')->andReturn('Demo');
$m->allows('setInput');
$m->allows('getSecretName')->andReturn('DEMO');
$m->allows('generator')->andReturn(base64_encode(random_bytes(64)));
$m->allows('info')->andReturnUsing(function ($v) {
echo $v;
});
$m->setInput($input);
$invoke->invoke($m);
$this->assertTrue(str_contains(file_get_contents($env), 'DEMO'));
}
}

0 comments on commit 294e168

Please sign in to comment.