Skip to content

Commit

Permalink
Support for API calls from the Web UI
Browse files Browse the repository at this point in the history
1. Remove support for cookie auth on authorization header and just use cookies.
2. Even if the API is disabled, it should still work for calls via cookie auth to serve the UI.
3. Add internal route for private APIs and use it for autocomplete functionality.
4. Remove xmlhttprequest.php / xmlhttprequest_api.php.
5. Add constants for headers, login methods and middleware attributes.
  • Loading branch information
vboctor committed Mar 19, 2017
1 parent 4cbb086 commit 47f6cb8
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 189 deletions.
7 changes: 5 additions & 2 deletions api/rest/index.php
Expand Up @@ -35,6 +35,7 @@
require_once( $t_restcore_dir . 'ApiEnabledMiddleware.php' );
require_once( $t_restcore_dir . 'AuthMiddleware.php' );
require_once( $t_restcore_dir . 'CacheMiddleware.php' );
require_once( $t_restcore_dir . 'OfflineMiddleware.php' );
require_once( $t_restcore_dir . 'VersionMiddleware.php' );

# Hint to re-used mantisconnect code that it is being executed from REST rather than SOAP.
Expand All @@ -44,12 +45,14 @@
$g_app = new \Slim\App();

# Add middleware - executed in reverse order of appearing here.
$g_app->add( new CacheMiddleware() );
$g_app->add( new AuthMiddleware() );
$g_app->add( new ApiEnabledMiddleware() );
$g_app->add( new AuthMiddleware() );
$g_app->add( new VersionMiddleware() );
$g_app->add( new OfflineMiddleware() );
$g_app->add( new CacheMiddleware() );

require_once( $t_restcore_dir . 'config_rest.php' );
require_once( $t_restcore_dir . 'internal_rest.php' );
require_once( $t_restcore_dir . 'issues_rest.php' );
require_once( $t_restcore_dir . 'lang_rest.php' );
require_once( $t_restcore_dir . 'users_rest.php' );
Expand Down
12 changes: 7 additions & 5 deletions api/rest/restcore/ApiEnabledMiddleware.php
Expand Up @@ -27,12 +27,14 @@
*/
class ApiEnabledMiddleware {
public function __invoke( \Slim\Http\Request $request, \Slim\Http\Response $response, callable $next ) {
if( mci_is_mantis_offline() ) {
return $response->withStatus( HTTP_STATUS_UNAVAILABLE, 'Mantis Offline' );
}
$t_force_enable = $request->getAttribute( ATTRIBUTE_FORCE_API_ENABLED );

if( config_get( 'webservice_rest_enabled' ) == OFF ) {
return $response->withStatus( HTTP_STATUS_UNAVAILABLE, 'Mantis REST API disabled.' );
# If request is coming from UI, then force enable will be true, hence, request shouldn't be blocked
# even if API is disabled.
if( !$t_force_enable ) {
if( config_get( 'webservice_rest_enabled' ) == OFF ) {
return $response->withStatus( HTTP_STATUS_UNAVAILABLE, 'API disabled' );
}
}

return $next( $request, $response );
Expand Down
49 changes: 26 additions & 23 deletions api/rest/restcore/AuthMiddleware.php
Expand Up @@ -23,42 +23,42 @@
*/

require_api( 'authentication_api.php' );
require_api( 'user_api.php' );

/**
* A middleware class that handles authentication and authorization to access APIs.
*/
class AuthMiddleware {
public function __invoke( \Slim\Http\Request $request, \Slim\Http\Response $response, callable $next ) {
$t_authorization_header = $request->getHeaderLine( 'Authorization' );

$t_password = '';
$t_authorization_header = $request->getHeaderLine( HEADER_AUTHORIZATION );

if( empty( $t_authorization_header ) ) {
$t_username = config_get( 'anonymous_account' );
# Since authorization header is empty, check if user is authenticated by checking the cookie
# This mode is used when Web UI javascript calls into the API.
if( auth_is_user_authenticated() ) {
$t_username = user_get_name( auth_get_current_user_id() );
$t_password = auth_get_current_user_cookie( /* auto-login-anonymous */ false );
$t_login_method = LOGIN_METHOD_COOKIE;
} else {
$t_username = config_get( 'anonymous_account' );

if( config_get( 'allow_anonymous_login' ) == OFF || empty( $t_username ) ) {
return $response->withStatus( HTTP_STATUS_FORBIDDEN, 'API token required' );
}
if( config_get( 'allow_anonymous_login' ) == OFF || empty( $t_username ) ) {
return $response->withStatus( HTTP_STATUS_UNAUTHORIZED, 'API token required' );
}

$t_login_method = 'anonymous';
$t_login_method = LOGIN_METHOD_ANONYMOUS;
$t_password = '';
}
} else {
# TODO: add an index on the token hash for the method below
$t_user_id = api_token_get_user( $t_authorization_header );
if( $t_user_id === false ) {
$t_user_id = auth_user_id_from_cookie( $t_authorization_header );
if( $t_user_id === false ) {
return $response->withStatus( HTTP_STATUS_FORBIDDEN, 'API token not found' );
}

# use cookie to login, useful for calls from web API.
$t_login_method = 'cookie';
$t_password = $t_authorization_header;
} else {
# use api token
$t_login_method = 'api-token';
$t_password = $t_authorization_header;
return $response->withStatus( HTTP_STATUS_FORBIDDEN, 'API token not found' );
}

# use api token
$t_login_method = LOGIN_METHOD_API_TOKEN;
$t_password = $t_authorization_header;
$t_username = user_get_name( $t_user_id );
}

Expand All @@ -67,11 +67,14 @@ public function __invoke( \Slim\Http\Request $request, \Slim\Http\Response $resp
}

# Now that user is logged in, check if they have the right access level to access the REST API.
if( !mci_has_readonly_access() ) {
# Don't treat web UI calls with cookies as API calls that need to be disabled for certain access levels.
if( $t_login_method != LOGIN_METHOD_COOKIE && !mci_has_readonly_access() ) {
return $response->withStatus( HTTP_STATUS_FORBIDDEN, 'Higher access level required for API access' );
}

return $next( $request, $response )->withHeader( 'X-Mantis-Username', $t_username )->
withHeader( 'X-Mantis-LoginMethod', $t_login_method );
$t_force_enable = $t_login_method == LOGIN_METHOD_COOKIE;
return $next( $request->withAttribute( ATTRIBUTE_FORCE_API_ENABLED, $t_force_enable ), $response )->
withHeader( HEADER_USERNAME, $t_username )->
withHeader( HEADER_LOGIN_METHOD, $t_login_method );
}
}
37 changes: 37 additions & 0 deletions api/rest/restcore/OfflineMiddleware.php
@@ -0,0 +1,37 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* A webservice interface to Mantis Bug Tracker
*
* @package MantisBT
* @copyright Copyright MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
*/

/**
* A middleware class that handles checks if MatnisBT is offline or not.
* If offline, then fail API calls.
*/
class OfflineMiddleware {
public function __invoke( \Slim\Http\Request $request, \Slim\Http\Response $response, callable $next ) {
if( mci_is_mantis_offline() ) {
return $response->withStatus( HTTP_STATUS_UNAVAILABLE, 'Mantis Offline' );
}

return $next( $request, $response );
}
}
2 changes: 1 addition & 1 deletion api/rest/restcore/VersionMiddleware.php
Expand Up @@ -28,6 +28,6 @@
class VersionMiddleware {
public function __invoke( \Slim\Http\Request $request, \Slim\Http\Response $response, callable $next )
{
return $next( $request, $response )->withHeader( 'X-Mantis-Version', mc_version() );
return $next( $request, $response )->withHeader( HEADER_VERSION, mc_version() );
}
}
64 changes: 64 additions & 0 deletions api/rest/restcore/internal_rest.php
@@ -0,0 +1,64 @@
<?php
# MantisBT - A PHP based bugtracking system

# MantisBT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# MantisBT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with MantisBT. If not, see <http://www.gnu.org/licenses/>.

/**
* A webservice interface to Mantis Bug Tracker
*
* @package MantisBT
* @copyright Copyright MantisBT Team - mantisbt-dev@lists.sourceforge.net
* @link http://www.mantisbt.org
*/

require_api( 'helper_api.php' );

/**
* WARNING: All APIs under the internal route are considered private and can break anytime.
*/
$g_app->group('/internal', function() use ( $g_app ) {
$g_app->any( '/autocomplete', 'rest_internal_autocomplete' );
});

/**
* A method that gets the auto-complete result for given field and prefix.
*
* @param \Slim\Http\Request $p_request The request.
* @param \Slim\Http\Response $p_response The response.
* @param array $p_args Arguments
* @return \Slim\Http\Response The augmented response.
*/
function rest_internal_autocomplete( \Slim\Http\Request $p_request, \Slim\Http\Response $p_response, array $p_args ) {
$t_field = $p_request->getParam( 'field' );;
$t_prefix = $p_request->getParam( 'prefix' );

switch( $t_field ) {
case 'platform':
$t_unique_entries = profile_get_field_all_for_user( 'platform' );
$t_matches = helper_filter_by_prefix( $t_unique_entries, $t_prefix );
break;
case 'os':
$t_unique_entries = profile_get_field_all_for_user( 'os' );
$t_matches = helper_filter_by_prefix( $t_unique_entries, $t_prefix );
break;
case 'os_build':
$t_unique_entries = profile_get_field_all_for_user( 'os_build' );
$t_matches = helper_filter_by_prefix( $t_unique_entries, $t_prefix );
break;
default:
return $p_response->withStatus( HTTP_STATUS_NOT_FOUND, "Field '$t_field' doesn't have auto-complete." );
}

return $p_response->withStatus( HTTP_STATUS_SUCCESS )->withJson( $t_matches );
}
4 changes: 3 additions & 1 deletion config_defaults_inc.php
Expand Up @@ -4704,7 +4704,9 @@
$g_webservice_version_when_not_found = '';

/**
* Whether the REST API (experimental) is enabled or not.
* Whether the REST API (experimental) is enabled or not. Note that this flag only
* impacts API Token based auth. Hence, even if the API is disabled, it can still be
* used from the Web UI using cookie based authentication.
*
* @global integer $g_webservice_rest_enabled
*/
Expand Down
15 changes: 15 additions & 0 deletions core/constant_inc.php
Expand Up @@ -664,10 +664,25 @@
define( 'HTTP_STATUS_CREATED', 201 );
define( 'HTTP_STATUS_NO_CONTENT', 204 );
define( 'HTTP_STATUS_BAD_REQUEST', 400 );
define( 'HTTP_STATUS_UNAUTHORIZED', 401 );
define( 'HTTP_STATUS_FORBIDDEN', 403 );
define( 'HTTP_STATUS_NOT_FOUND', 404 );
define( 'HTTP_STATUS_CONFLICT', 409 );
define( 'HTTP_STATUS_INTERNAL_SERVER_ERROR', 500 );
define( 'HTTP_STATUS_UNAVAILABLE', 503 );

# HTTP HEADERS
define( 'HEADER_AUTHORIZATION', 'Authorization' );
define( 'HEADER_LOGIN_METHOD', 'X-Mantis-LoginMethod' );
define( 'HEADER_USERNAME', 'X-Mantis-Username' );
define( 'HEADER_VERSION', 'X-Mantis-Version' );

# LOGIN METHODS
define( 'LOGIN_METHOD_COOKIE', 'cookie' );
define( 'LOGIN_METHOD_API_TOKEN', 'api-token' );
define( 'LOGIN_METHOD_ANONYMOUS', 'anonymous' );

# SLIM FRAMEWORK ATTRIBUTES
define( 'ATTRIBUTE_FORCE_API_ENABLED', 'force_enable_api' );


17 changes: 17 additions & 0 deletions core/helper_api.php
Expand Up @@ -677,3 +677,20 @@ function helper_duration_to_minutes( $p_hhmm ) {
function shutdown_functions_register() {
register_shutdown_function( 'email_shutdown_function' );
}

/**
* Filter a set of strings by finding strings that start with a case-insensitive prefix.
* @param array $p_set An array of strings to search through.
* @param string $p_prefix The prefix to filter by.
* @return array An array of strings which match the supplied prefix.
*/
function helper_filter_by_prefix( array $p_set, $p_prefix ) {
$t_matches = array();
foreach ( $p_set as $p_item ) {
if( utf8_strtolower( utf8_substr( $p_item, 0, utf8_strlen( $p_prefix ) ) ) === utf8_strtolower( $p_prefix ) ) {
$t_matches[] = $p_item;
}
}
return $t_matches;
}

99 changes: 0 additions & 99 deletions core/xmlhttprequest_api.php

This file was deleted.

0 comments on commit 47f6cb8

Please sign in to comment.