mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
API, batch: add allowed_list to narrow use
- only allows processing batch calls to the urls that match the regexs in allowed_list controlling/decreasing the scope of the middleware
This commit is contained in:
@@ -3,7 +3,10 @@
|
||||
import io
|
||||
from urlparse import urlparse
|
||||
import json
|
||||
import re
|
||||
|
||||
from paste import httpexceptions
|
||||
import routes
|
||||
|
||||
import logging
|
||||
log = logging.getLogger( __name__ )
|
||||
@@ -13,7 +16,11 @@ class BatchMiddleware( object ):
|
||||
"""
|
||||
"""
|
||||
DEFAULT_CONFIG = {
|
||||
'route' : '/api/batch'
|
||||
'route' : '/api/batch',
|
||||
'allowed_routes' : [
|
||||
'^api\/users.*',
|
||||
'^api\/histories.*',
|
||||
]
|
||||
}
|
||||
|
||||
def __init__( self, galaxy, application, config=None ):
|
||||
@@ -21,7 +28,9 @@ class BatchMiddleware( object ):
|
||||
self.galaxy = galaxy
|
||||
#: the wrapped webapp
|
||||
self.application = application
|
||||
self.config = config or self.DEFAULT_CONFIG
|
||||
self.config = self.DEFAULT_CONFIG.copy()
|
||||
self.config.update( config )
|
||||
self.base_url = routes.url_for( '/' )
|
||||
self.handle_request = self.galaxy.handle_request
|
||||
|
||||
def __call__( self, environ, start_response ):
|
||||
@@ -35,7 +44,10 @@ class BatchMiddleware( object ):
|
||||
|
||||
responses = []
|
||||
for request in requests:
|
||||
if not self._valid( request ):
|
||||
print '------------------------'
|
||||
print request
|
||||
if not self._is_allowed_route( request[ 'url' ] ):
|
||||
responses.append( self._disallowed_route_response( request[ 'url' ] ) )
|
||||
continue
|
||||
|
||||
request_environ = self._build_request_environ( batch_environ, request )
|
||||
@@ -57,9 +69,28 @@ class BatchMiddleware( object ):
|
||||
payload = json.loads( request_body )
|
||||
return payload
|
||||
|
||||
def _valid( self, request ):
|
||||
def _is_allowed_route( self, route ):
|
||||
if self.config.get( 'allowed_routes', None ):
|
||||
print self.base_url
|
||||
shortened_route = route.replace( self.base_url, '', 1 )
|
||||
matches = [ re.match( allowed, shortened_route ) for allowed in self.config[ 'allowed_routes' ] ]
|
||||
print matches
|
||||
return any( matches )
|
||||
return True
|
||||
|
||||
def _disallowed_route_response( self, route ):
|
||||
return dict( status=403, headers=self._default_headers(), body={
|
||||
'err_msg' : 'Disallowed route used for batch operation',
|
||||
'route' : route,
|
||||
'allowed' : self.config[ 'allowed_routes' ]
|
||||
})
|
||||
|
||||
def _create_invalid_batch_response( self ):
|
||||
return dict( status=403, headers=self._default_headers(), body={
|
||||
"err_msg" : "Disallowed route used for batch operation",
|
||||
"allowed" : self.allowed_routes,
|
||||
})
|
||||
|
||||
def _build_request_environ( self, original_environ, request ):
|
||||
"""
|
||||
Given a request and the original environ used to call the batch, return
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+30788
-16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,57 @@
|
||||
webpackJsonp([4],[function(e,r,n){(function(e){var r=n(1),a=r,i=n(58).GalaxyApp,o=n(11),t=n(5),l=n(60);window.app=function(n,c){window.Galaxy=new i(n,c),Galaxy.debug("login app");var d=encodeURI(n.redirect);if(!n.show_welcome_with_login){var w=r.param({use_panels:"True",redirect:d});return void(window.location.href=Galaxy.root+"user/login?"+w)}var p=new l.PageLayoutView(e.extend(n,{el:"body",center:new o.CenterPanel({el:"#center"}),right:new o.RightPanel({title:t("Login required"),el:"#right"})}));a(function(){var e=r.param({redirect:d}),a=Galaxy.root+"user/login?"+e;p.render(),p.center.$("#galaxy_main").prop("src",n.welcome_url),p.right.$(".unified-panel-body").css("overflow","hidden").html('<iframe src="'+a+'" frameborder="0" style="width: 100%; height: 100%;"/>')})}}).call(r,n(2))}]);
|
||||
webpackJsonp([5],[
|
||||
/* 0 */
|
||||
/*!**************************************!*\
|
||||
!*** ./galaxy/scripts/apps/login.js ***!
|
||||
\**************************************/
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
/* WEBPACK VAR INJECTION */(function(_) {
|
||||
var jQuery = __webpack_require__( /*! jquery */ 3 ),
|
||||
$ = jQuery,
|
||||
GalaxyApp = __webpack_require__( /*! galaxy */ 4 ).GalaxyApp,
|
||||
PANEL = __webpack_require__( /*! layout/panel */ 12 ),
|
||||
_l = __webpack_require__( /*! utils/localization */ 7 ),
|
||||
PAGE = __webpack_require__( /*! layout/page */ 98 );
|
||||
|
||||
window.app = function app( options, bootstrapped ){
|
||||
window.Galaxy = new GalaxyApp( options, bootstrapped );
|
||||
Galaxy.debug( 'login app' );
|
||||
var redirect = encodeURI( options.redirect );
|
||||
|
||||
// TODO: remove iframe for user login (at least) and render login page from here
|
||||
// then remove this redirect
|
||||
if( !options.show_welcome_with_login ){
|
||||
var params = jQuery.param({ use_panels : 'True', redirect : redirect });
|
||||
window.location.href = Galaxy.root + 'user/login?' + params;
|
||||
return;
|
||||
}
|
||||
|
||||
var loginPage = new PAGE.PageLayoutView( _.extend( options, {
|
||||
el : 'body',
|
||||
center : new PANEL.CenterPanel({ el : '#center' }),
|
||||
right : new PANEL.RightPanel({
|
||||
title : _l( 'Login required' ),
|
||||
el : '#right'
|
||||
}),
|
||||
}));
|
||||
|
||||
$(function(){
|
||||
// TODO: incorporate *actual* referrer/redirect info as the original page does
|
||||
var params = jQuery.param({ redirect : redirect }),
|
||||
loginUrl = Galaxy.root + 'user/login?' + params;
|
||||
loginPage.render();
|
||||
|
||||
// welcome page (probably) needs to remain sandboxed
|
||||
loginPage.center.$( '#galaxy_main' ).prop( 'src', options.welcome_url );
|
||||
|
||||
loginPage.right.$( '.unified-panel-body' )
|
||||
.css( 'overflow', 'hidden' )
|
||||
.html( '<iframe src="' + loginUrl + '" frameborder="0" style="width: 100%; height: 100%;"/>' );
|
||||
});
|
||||
};
|
||||
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(/*! underscore */ 1)))
|
||||
|
||||
/***/ }
|
||||
]);
|
||||
//# sourceMappingURL=login.bundled.js.map
|
||||
File diff suppressed because one or more lines are too long
+39
-12
@@ -1,11 +1,9 @@
|
||||
import json
|
||||
from requests import post
|
||||
import pprint
|
||||
|
||||
from base import api
|
||||
# from .helpers import DatasetPopulator
|
||||
|
||||
import logging
|
||||
from requests import post
|
||||
from base import api
|
||||
|
||||
log = logging.getLogger( "functional_tests.py" )
|
||||
|
||||
|
||||
@@ -15,15 +13,17 @@ class ApiBatchTestCase( api.ApiTestCase ):
|
||||
return self.galaxy_interactor.api_key if not admin else self.galaxy_interactor.master_api_key
|
||||
|
||||
def _with_key( self, url, admin=False ):
|
||||
return url + '?key=' + self._get_api_key( admin=admin )
|
||||
sep = '&' if '?' in url else '?'
|
||||
return url + sep + 'key=' + self._get_api_key( admin=admin )
|
||||
|
||||
def _post_batch( self, batch ):
|
||||
data = json.dumps({ "batch" : batch })
|
||||
return post( "%s/batch" % ( self.galaxy_interactor.api_url ), data=data )
|
||||
|
||||
def log_reponse( self, response ):
|
||||
def _log_reponse( self, response ):
|
||||
log.debug( 'RESPONSE %s\n%s', ( '-' * 40 ), pprint.pformat( response ) )
|
||||
|
||||
# ---- tests
|
||||
def test_simple_array( self ):
|
||||
batch = [
|
||||
dict( url=self._with_key( '/api/histories' ) ),
|
||||
@@ -33,17 +33,26 @@ class ApiBatchTestCase( api.ApiTestCase ):
|
||||
]
|
||||
response = self._post_batch( batch )
|
||||
response = response.json()
|
||||
# self.log_reponse( response )
|
||||
# self._log_reponse( response )
|
||||
self.assertIsInstance( response, list )
|
||||
self.assertEquals( len( response ), 3 )
|
||||
|
||||
def test_bad_route( self ):
|
||||
def test_unallowed_route( self ):
|
||||
batch = [
|
||||
dict( url=self._with_key( '/api/bler' ) )
|
||||
dict( url=self._with_key( '/api/workflow' ) )
|
||||
]
|
||||
response = self._post_batch( batch )
|
||||
response = response.json()
|
||||
self.assertIsInstance( response, list )
|
||||
self.assertEquals( response[0][ 'status' ], 403 )
|
||||
|
||||
def test_404_route( self ):
|
||||
# needs to be within the allowed routes
|
||||
batch = [
|
||||
dict( url=self._with_key( '/api/histories_bler' ) )
|
||||
]
|
||||
response = self._post_batch( batch )
|
||||
response = response.json()
|
||||
# self.log_reponse( response )
|
||||
self.assertIsInstance( response, list )
|
||||
self.assertEquals( response[0][ 'status' ], 404 )
|
||||
|
||||
@@ -54,7 +63,25 @@ class ApiBatchTestCase( api.ApiTestCase ):
|
||||
]
|
||||
response = self._post_batch( batch )
|
||||
response = response.json()
|
||||
# self.log_reponse( response )
|
||||
# self._log_reponse( response )
|
||||
self.assertIsInstance( response, list )
|
||||
self.assertEquals( response[0][ 'status' ], 400 )
|
||||
self.assertEquals( response[1][ 'status' ], 501 )
|
||||
|
||||
def test_querystring_params( self ):
|
||||
post_data = dict( name='test' )
|
||||
create_response = self._post( 'histories', data=post_data ).json()
|
||||
|
||||
history_url = '/api/histories/' + create_response[ 'id' ]
|
||||
history_url_with_keys = history_url + '?v=dev&keys=size,non_ready_jobs'
|
||||
contents_url_with_filters = history_url + '/contents?v=dev&q=deleted&qv=True'
|
||||
batch = [
|
||||
dict( url=self._with_key( history_url_with_keys ) ),
|
||||
dict( url=self._with_key( contents_url_with_filters ) ),
|
||||
]
|
||||
response = self._post_batch( batch )
|
||||
response = response.json()
|
||||
self._log_reponse( response )
|
||||
self.assertEquals( len( response ), 2 )
|
||||
self.assertEquals( len( response[0][ 'body' ].keys() ), 2 )
|
||||
self.assertEquals( response[1][ 'body' ], [] )
|
||||
|
||||
Reference in New Issue
Block a user