diff --git a/api/ApiOATHValidate.php b/api/ApiOATHValidate.php new file mode 100644 index 00000000..2ce162ee --- /dev/null +++ b/api/ApiOATHValidate.php @@ -0,0 +1,111 @@ +requirePostedParameters( [ 'totp', 'token' ] ); + + $params = $this->extractRequestParams(); + if ( $params['user'] === null ) { + $params['user'] = $this->getUser()->getName(); + } + + if ( !$this->getUser()->isAllowed( 'oathauth-api-all' ) ) { + $this->dieUsage( + 'You do not have permission to validate an OATH token', + 'permissiondenied' + ); + } + + $user = User::newFromName( $params['user'] ); + if ( $user === false ) { + $this->dieUsageMsg( [ 'noname', $params['user'] ] ); + } + + // Don't increase pingLimiter, just check for limit exceeded + if ( $user->pingLimiter( 'badoath', 0 ) ) { + $this->dieUsageMsg( 'actionthrottledtext' ); + } + + $result = [ + ApiResult::META_BC_BOOLS => [ 'enabled', 'valid' ], + 'enabled' => false, + 'valid' => false, + ]; + + if ( !$user->isAnon() ) { + $oathUser = OATHAuthHooks::getOATHUserRepository() + ->findByUser( $user ); + if ( $oathUser ) { + $key = $oathUser->getKey(); + if ( $key !== null ) { + $result['enabled'] = true; + $result['valid'] = $key->verifyToken( + $params['totp'], $oathUser ) !== false; + } + } + } + + if ( !$result['valid'] ) { + // Increase rate limit counter for failed request + $user->pingLimiter( 'badoath' ); + } + + $this->getResult()->addValue( null, $this->getModuleName(), $result ); + } + + public function getCacheMode( $params ) { + return 'private'; + } + + public function isInternal() { + return true; + } + + public function needsToken() { + return 'csrf'; + } + + public function getAllowedParams() { + return [ + 'user' => [ + ApiBase::PARAM_TYPE => 'user', + ], + 'totp' => [ + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ], + ]; + } + + protected function getExamplesMessages() { + return [ + 'action=oathvalidate&totp=123456&token=123ABC' + => 'apihelp-oath-example-1', + 'action=oathvalidate&user=Example&totp=123456&token=123ABC' + => 'apihelp-oath-example-2', + ]; + } +} diff --git a/api/ApiQueryOATH.php b/api/ApiQueryOATH.php index 2a20d2c3..6c1629a2 100644 --- a/api/ApiQueryOATH.php +++ b/api/ApiQueryOATH.php @@ -51,6 +51,7 @@ class ApiQueryOATH extends ApiQueryBase { $result = $this->getResult(); $data = [ + ApiResult::META_BC_BOOLS => [ 'enabled' ], 'enabled' => false, ]; diff --git a/extension.json b/extension.json index ed21abf9..d1bc56f6 100644 --- a/extension.json +++ b/extension.json @@ -7,6 +7,7 @@ "type": "other", "license-name": "GPL-2.0+", "AutoloadClasses": { + "ApiOATHValidate": "api/ApiOATHValidate.php", "ApiQueryOATH": "api/ApiQueryOATH.php", "OATHAuthHooks": "OATHAuth.hooks.php", "OATHAuthLegacyHooks": "OATHAuth.hooks.legacy.php", @@ -90,8 +91,19 @@ "GrantPermissionGroups": { "oath": "authentication" }, + "APIModules": { + "oathvalidate": "ApiOATHValidate" + }, "APIMetaModules": { "oath": "ApiQueryOATH" }, + "RateLimits": { + "badoath": { + "user": [ + 10, + 60 + ] + } + }, "manifest_version": 1 } diff --git a/i18n/en.json b/i18n/en.json index 1970e7a5..a49b8ec0 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -58,5 +58,10 @@ "apihelp-query+oath-description": "Check to see if two-factor authentication (OATH) is enabled for a user.", "apihelp-query+oath-param-user": "User to get information about. Defaults to the current user.", "apihelp-query+oath-example-1": "Get information about the current user", - "apihelp-query+oath-example-2": "Get information about user Example" + "apihelp-query+oath-example-2": "Get information about user Example", + "apihelp-oathvalidate-description": "Validate a two-factor authentication (OATH) token.", + "apihelp-oathvalidate-param-user": "User to validate token for. Defaults to the current user.", + "apihelp-oathvalidate-param-totp": "Two-factor authentication (OATH) token.", + "apihelp-oath-example-1": "Validate a token for the current user", + "apihelp-oath-example-2": "Validate a token for the user Example" } diff --git a/i18n/qqq.json b/i18n/qqq.json index 96c00acd..ddf548db 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -63,5 +63,10 @@ "apihelp-query+oath-description": "{{doc-apihelp-description|query+oath}}", "apihelp-query+oath-param-user": "{{doc-apihelp-param|query+oath|user}}", "apihelp-query+oath-example-1": "{{doc-apihelp-example|query+oath}}", - "apihelp-query+oath-example-2": "{{doc-apihelp-example|query+oath}}" + "apihelp-query+oath-example-2": "{{doc-apihelp-example|query+oath}}", + "apihelp-oathvalidate-description": "{{doc-apihelp-description|oathvalidate}}", + "apihelp-oathvalidate-param-user": "{{doc-apihelp-param|oathvalidate|user}}", + "apihelp-oathvalidate-param-totp": "{{doc-apihelp-param|oathvalidate|totp}}", + "apihelp-oath-example-1": "{{doc-apihelp-example|oathvalidate}}", + "apihelp-oath-example-2": "{{doc-apihelp-example|oathvalidate}}" }