Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.00% covered (warning)
75.00%
48 / 64
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccountLoginLogic
75.00% covered (warning)
75.00%
48 / 64
75.00% covered (warning)
75.00%
3 / 4
17.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 startup
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 validateImpl
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 executeImpl
68.00% covered (warning)
68.00%
34 / 50
0.00% covered (danger)
0.00%
0 / 1
10.10
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\App\Models\Domain\Page\Account;
6
7use PeServer\App\Models\AuditLog;
8use PeServer\App\Models\Dao\Domain\UserDomainDao;
9use PeServer\App\Models\Dao\Entities\UserAuthenticationsEntityDao;
10use PeServer\App\Models\Dao\Entities\UsersEntityDao;
11use PeServer\App\Models\Data\SessionAccount;
12use PeServer\App\Models\Data\SessionAnonymous;
13use PeServer\App\Models\Domain\Page\PageLogicBase;
14use PeServer\App\Models\Domain\Page\SessionAnonymousTrait;
15use PeServer\App\Models\Domain\UserLevel;
16use PeServer\App\Models\SessionKey;
17use PeServer\Core\Cryptography;
18use PeServer\Core\Http\HttpStatus;
19use PeServer\Core\I18n;
20use PeServer\Core\Mvc\LogicCallMode;
21use PeServer\Core\Mvc\LogicParameter;
22use PeServer\Core\Mvc\Validator;
23use PeServer\Core\Web\WebSecurity;
24use PeServer\Core\Text;
25
26class AccountLoginLogic extends PageLogicBase
27{
28    use SessionAnonymousTrait;
29
30    #region define
31
32    private const ERROR_LOGIN_PARAMETER = 'error/login_parameter';
33
34    #endregion
35
36    public function __construct(LogicParameter $parameter)
37    {
38        parent::__construct($parameter);
39    }
40
41    #region PageLogicBase
42
43    protected function startup(LogicCallMode $callMode): void
44    {
45        $this->registerParameterKeys([
46            'account_login_login_id',
47            'account_login_password',
48        ], false);
49    }
50
51    protected function validateImpl(LogicCallMode $callMode): void
52    {
53        if ($callMode === LogicCallMode::Initialize) {
54            return;
55        }
56
57        $this->throwHttpStatusIfNotLogin(HttpStatus::NotFound);
58
59        $loginId = $this->getRequest('account_login_login_id');
60        if (Text::isNullOrWhiteSpace($loginId)) {
61            $this->addCommonError(I18n::message(self::ERROR_LOGIN_PARAMETER));
62        }
63
64        $password = $this->getRequest('account_login_password');
65        if (Text::isNullOrWhiteSpace($password)) {
66            $this->addCommonError(I18n::message(self::ERROR_LOGIN_PARAMETER));
67        }
68    }
69
70    protected function executeImpl(LogicCallMode $callMode): void
71    {
72        if ($callMode === LogicCallMode::Initialize) {
73            $this->setSession(SessionKey::ANONYMOUS, new SessionAnonymous(login: true));
74            return;
75        }
76
77        $database = $this->openDatabase();
78
79        $usersEntityDao = new UsersEntityDao($database);
80        $userAuthenticationsEntityDao = new UserAuthenticationsEntityDao($database);
81        $userDomainDao = new UserDomainDao($database);
82
83        $existsSetupUser = $usersEntityDao->selectExistsSetupUser();
84        if ($existsSetupUser) {
85            $this->logger->info('セットアップ ユーザー 検証');
86        } else {
87            $this->logger->info('通常 ユーザー 検証');
88        }
89
90        $user = $userDomainDao->selectLoginUser($this->getRequest('account_login_login_id'));
91
92        if ($user === null) {
93            $this->addCommonError(I18n::message(self::ERROR_LOGIN_PARAMETER));
94            return;
95        }
96
97        if ($existsSetupUser && $user->level !== UserLevel::SETUP) {
98            $this->addCommonError(I18n::message(self::ERROR_LOGIN_PARAMETER));
99            $this->logger->error('未セットアップ状態での通常ログインは抑制中');
100            return;
101        }
102
103        $loginRawPassword = $this->getRequest('account_login_password');
104        // パスワード突合
105        $verifyOk = Cryptography::verifyPassword($loginRawPassword, $user->currentPassword);
106        if (!$verifyOk) {
107            $this->addCommonError(I18n::message(self::ERROR_LOGIN_PARAMETER));
108            $this->logger->warn('ログイン失敗: {0}', $user->userId);
109            $this->writeAuditLogTargetUser($user->userId, AuditLog::LOGIN_FAILED);
110            return;
111        }
112        // パスワードのアルゴリズムが古い場合に再設定する(業務ロジックのポリシー云々ではない)
113        $isNeedRehashPassword = Cryptography::needsRehashPassword($user->currentPassword);
114        if ($isNeedRehashPassword) {
115            $info = Cryptography::getPasswordInformation($user->currentPassword);
116            $this->logger->info("[OLD] password needs rehash: {0}, {1}", $user->userId, $info);
117            $loginNewPassword = Cryptography::hashPassword($loginRawPassword);
118            $database->transaction(function ($context) use ($user, $loginNewPassword) {
119                $userAuthenticationsEntityDao = new UserAuthenticationsEntityDao($context);
120                $userAuthenticationsEntityDao->updatePasswordOnly($user->userId, $loginNewPassword);
121                $info = Cryptography::getPasswordInformation($loginNewPassword);
122                $this->logger->info("[NEW] password needs rehash: {0}, {1}", $user->userId, $info);
123                return true;
124            });
125        }
126
127        $this->removeSession(self::SESSION_ALL_CLEAR);
128        $account = new SessionAccount(
129            $user->userId,
130            $user->loginId,
131            $user->name,
132            $user->level,
133            $user->state
134        );
135        $this->setSession(SessionKey::ACCOUNT, $account);
136        $this->restartSession();
137        $this->writeAuditLogCurrentUser(AuditLog::LOGIN_SUCCESS, $account);
138
139        $userAuthenticationsEntityDao->updateClearReminder($account->userId);
140    }
141
142    #endregion
143}