Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
59.65% |
34 / 57 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ApiOATHValidate | |
59.65% |
34 / 57 |
|
0.00% |
0 / 6 |
29.78 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
91.89% |
34 / 37 |
|
0.00% |
0 / 1 |
10.05 | |||
isInternal | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
needsToken | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllowedParams | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getExamplesMessages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace MediaWiki\Extension\OATHAuth\Api\Module; |
20 | |
21 | use MediaWiki\Api\ApiBase; |
22 | use MediaWiki\Api\ApiMain; |
23 | use MediaWiki\Api\ApiResult; |
24 | use MediaWiki\Extension\OATHAuth\OATHUserRepository; |
25 | use MediaWiki\Json\FormatJson; |
26 | use MediaWiki\Logger\LoggerFactory; |
27 | use MediaWiki\User\UserFactory; |
28 | use Wikimedia\ParamValidator\ParamValidator; |
29 | |
30 | /** |
31 | * Validate an OATH token. |
32 | * |
33 | * @ingroup API |
34 | * @ingroup Extensions |
35 | */ |
36 | class ApiOATHValidate extends ApiBase { |
37 | private OATHUserRepository $oathUserRepository; |
38 | private UserFactory $userFactory; |
39 | |
40 | public function __construct( |
41 | ApiMain $mainModule, |
42 | string $moduleName, |
43 | OATHUserRepository $oathUserRepository, |
44 | UserFactory $userFactory |
45 | ) { |
46 | parent::__construct( $mainModule, $moduleName ); |
47 | $this->oathUserRepository = $oathUserRepository; |
48 | $this->userFactory = $userFactory; |
49 | } |
50 | |
51 | public function execute() { |
52 | $this->requirePostedParameters( [ 'token', 'data' ] ); |
53 | // messages used: right-oathauth-api-all, action-oathauth-api-all, |
54 | $this->checkUserRightsAny( 'oathauth-api-all' ); |
55 | |
56 | $params = $this->extractRequestParams(); |
57 | if ( $params['user'] === null ) { |
58 | $user = $this->getUser(); |
59 | } else { |
60 | $user = $this->userFactory->newFromName( $params['user'] ); |
61 | if ( $user === null ) { |
62 | $this->dieWithError( 'noname' ); |
63 | } |
64 | } |
65 | |
66 | // Don't increase pingLimiter, just check for limit exceeded. |
67 | if ( $user->pingLimiter( 'badoath', 0 ) ) { |
68 | $this->dieWithError( 'apierror-ratelimited' ); |
69 | } |
70 | |
71 | $result = [ |
72 | ApiResult::META_BC_BOOLS => [ 'enabled', 'valid' ], |
73 | 'enabled' => false, |
74 | 'valid' => false, |
75 | ]; |
76 | |
77 | if ( $user->isNamed() ) { |
78 | $authUser = $this->oathUserRepository->findByUser( $user ); |
79 | if ( $authUser->isTwoFactorAuthEnabled() ) { |
80 | $result['enabled'] = true; |
81 | |
82 | $data = []; |
83 | $decoded = FormatJson::decode( $params['data'], true ); |
84 | if ( is_array( $decoded ) ) { |
85 | $data = $decoded; |
86 | } |
87 | |
88 | foreach ( $authUser->getKeys() as $key ) { |
89 | if ( $key->verify( $data, $authUser ) !== false ) { |
90 | $result['valid'] = true; |
91 | break; |
92 | } |
93 | } |
94 | |
95 | if ( !$result['valid'] ) { |
96 | // Increase rate limit counter for failed request |
97 | $user->pingLimiter( 'badoath' ); |
98 | |
99 | LoggerFactory::getInstance( 'authentication' )->info( |
100 | 'OATHAuth user {user} failed OTP token/recovery code from {clientip}', |
101 | [ |
102 | 'user' => $user, |
103 | 'clientip' => $user->getRequest()->getIP(), |
104 | ] |
105 | ); |
106 | } |
107 | } |
108 | } |
109 | |
110 | $this->getResult()->addValue( null, $this->getModuleName(), $result ); |
111 | } |
112 | |
113 | /** @inheritDoc */ |
114 | public function isInternal() { |
115 | return true; |
116 | } |
117 | |
118 | /** @inheritDoc */ |
119 | public function needsToken() { |
120 | return 'csrf'; |
121 | } |
122 | |
123 | /** |
124 | * @return array |
125 | */ |
126 | public function getAllowedParams() { |
127 | return [ |
128 | 'user' => [ |
129 | ParamValidator::PARAM_TYPE => 'user', |
130 | ], |
131 | 'data' => [ |
132 | ParamValidator::PARAM_TYPE => 'string', |
133 | ParamValidator::PARAM_REQUIRED => true, |
134 | ] |
135 | ]; |
136 | } |
137 | |
138 | /** |
139 | * @return array |
140 | */ |
141 | protected function getExamplesMessages() { |
142 | return [ |
143 | 'action=oathvalidate&data={"token":"123456"}&token=123ABC' |
144 | => 'apihelp-oathvalidate-example-1', |
145 | 'action=oathvalidate&user=Example&data={"token":"123456"}&token=123ABC' |
146 | => 'apihelp-oathvalidate-example-3', |
147 | ]; |
148 | } |
149 | } |