def test_different_key_prefixes(): """ Test using different key_prefixes """ limiter1 = Limiter( key_func=get_remote_addr, default_limits=["10 per hour", "1 per second"], config={ 'RATELIMIT_KEY_PREFIX': 'app1' } ) @limiter1.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' app1 = API(middleware=limiter1.middleware) app1.add_route('/things', ThingsResource()) client1 = testing.TestClient(app1) r = client1.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client1.simulate_get('/things') assert r.status == HTTP_429 ##### # app2 - with a different key limiter2 = Limiter( key_func=get_remote_addr, default_limits=["10 per hour", "1 per second"], config={ 'RATELIMIT_KEY_PREFIX': 'app2' } ) @limiter2.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' app2 = API(middleware=limiter2.middleware) app2.add_route('/things', ThingsResource()) client2 = testing.TestClient(app2) r = client2.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client2.simulate_get('/things') assert r.status == HTTP_429
def get_falcon_api( broker_resource: BrokerResource, settings: Settings = Settings(), use_fallback_sessions: bool = False, ) -> API: logging.basicConfig(level=settings.logging.level.value) beaker_settings = { "session.type": settings.beaker.type, "session.cookie_expires": True, "session.auto": True, # We got to have this on as things are currently programmed. "session.key": "JSESSIONID", "session.secure": True, "session.httponly": True, "session.data_dir": settings.beaker.data_dir, } beaker_middleware = None if not use_fallback_sessions: beaker_middleware = BeakerSessionMiddleware(beaker_settings) else: beaker_middleware = FallbackSessionMiddleware(beaker_settings) api = API(middleware=beaker_middleware, response_type=CookieCaseFixedResponse) api.add_route("/pcoip-broker/xml", resource=broker_resource) return api
def app(request, limiter): """ Creates a Falcon app with the default limiter """ @limiter.limit() class ThingsResource: # unmarked methods will use the default limit def on_get(self, req, resp): resp.body = 'Hello world!' # mark this method with a special limit # which will overwrite the default @limiter.limit(limits="1 per day") def on_post(self, req, resp): pass # a resource with no limits: class ThingsResourceNoLimit: # unmarked methods will use the default limit def on_get(self, req, resp): pass # add the limiter middleware to the Falcon app app = API(middleware=limiter.middleware) things = ThingsResource() thingsnolimit = ThingsResourceNoLimit() app.add_route('/things', things) app.add_route('/thingsnolimit', thingsnolimit) return app
def test_caching_content_type(caches, eviction_strategy): """ Testing that the Content-Type header gets cached even when it is not the default, but it is set in the responder """ # get the cache for the given eviction strategy cache = caches[eviction_strategy] # a resource where a custom response Cache-Type is set class CachedResource: @cache.cached(timeout=1) def on_get(self, req, resp): resp.content_type = 'mycustom/verycustom' if FALCONVERSION_MAIN < 3: resp.body = json.dumps({'num': random.randrange(0, 100000)}) else: resp.text = json.dumps({'num': random.randrange(0, 100000)}) app = API(middleware=cache.middleware) app.add_route('/randrange_cached', CachedResource()) client = testing.TestClient(app) # the first call will cache it result1 = client.simulate_get('/randrange_cached') assert result1.headers['Content-Type'] == 'mycustom/verycustom' # the second call returns it from cache - but it still carries # the same content-type result2 = client.simulate_get('/randrange_cached') assert result1.json['num'] == result2.json['num'] assert result2.headers['Content-Type'] == 'mycustom/verycustom'
def make_api(self): self.before_hooks.extend(self.hooks.optional_request_hooks()) self.after_hooks.extend(self.hooks.optional_response_hooks()) api = API(before=self.before_hooks, after=self.after_hooks) # Set the default route to the NYI object api.add_sink(self.default_route or NYI(before=self.before_hooks, after=self.after_hooks)) # Add Error Handlers - ordered generic to more specific built_in_handlers = [(Exception, handle_unexpected_errors), (ResponseException, ResponseException.handle), (InvalidTokenError, InvalidTokenError.handle)] for ex, handler in built_in_handlers + self._error_handlers: api.add_error_handler(ex, wrap_handler_with_hooks(handler, self.after_hooks)) # Add all the routes collected thus far for _, disp in self._dispatchers.items(): for endpoint, handler in disp.get_routes(): LOG.debug("Loading endpoint %s", endpoint) api.add_route(endpoint, handler) api.add_route('%s.json' % endpoint, handler) return api
def test_redis(redis_server): """ Test using the redis backend """ limiter = Limiter( key_func=get_remote_addr, default_limits=["10 per hour", "1 per second"], config={ 'RATELIMIT_KEY_PREFIX': 'myapp', 'RATELIMIT_STORAGE_URL': f'redis://@localhost:{REDIS_PORT}' } ) @limiter.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def hydrated_client(): """Create a Falcon API with an endpoint for testing Marshmallow""" data_store = DataStore() class PhilosopherResource: schema = Philosopher() def on_get(self, req, resp, phil_id): """Get a philosopher""" req.context["result"] = data_store.get(phil_id) class PhilosopherCollection: schema = Philosopher() def on_post(self, req, resp): req.context["result"] = data_store.insert(req.context["json"]) app = API(middleware=[m.Marshmallow()]) app.add_route("/philosophers", PhilosopherCollection()) app.add_route("/philosophers/{phil_id}", PhilosopherResource()) yield testing.TestClient(app) data_store.clear()
def test_empty_requests_dropped(self, empty_body, endpoint, endpoint_exists): # type: (bool, str, bool) -> None """Test that empty requests are dropped before routing Note that content_length is None if not explicitly set, so HEAD, GET, etc. requests are fine. """ client = testing.TestClient(API(middleware=[m.EmptyRequestDropper()])) # We have to forge the content-length header, because if it is # made automatically for our empty body, we won't get to the check # of the body contents. heads = {"Content-Length": str("20")} if empty_body else {} body = "" if empty_body else None res = client.simulate_get(endpoint, body=body, headers=heads) # type: testing.Result if empty_body: assert res.status == status_codes.HTTP_BAD_REQUEST else: if endpoint_exists: assert bool( 200 <= res.status_code < 300 or res.status_code == status_codes.HTTP_METHOD_NOT_ALLOWED) else: assert res.status == status_codes.HTTP_NOT_FOUND
def hydrated_client_multiple_middleware(): """Create a server for testing, with all included middlewares""" data_store = DataStore() class PhilosopherResource: schema = Philosopher() def on_get(self, req, resp, phil_id): """Get a philosopher""" req.context["result"] = data_store.get(phil_id) class PhilosopherCollection: schema = Philosopher() def on_post(self, req, resp): req.context["result"] = data_store.insert(req.context["json"]) app = API(middleware=[ m.Marshmallow(), m.JSONEnforcer(), m.EmptyRequestDropper() ]) app.add_route("/philosophers", PhilosopherCollection()) app.add_route("/philosophers/{phil_id}", PhilosopherResource()) yield testing.TestClient(app) data_store.clear()
def test_limit_by_resource_and_method(): """ Test using a custom key_func - one which creates different buckets by resource and method """ def get_key(req, resp, resource, params) -> str: user_key = get_remote_addr(req, resp, resource, params) return f"{user_key}:{resource.__class__.__name__}:{req.method}" limiter = Limiter( key_func=get_key, default_limits=["10 per hour", "1 per second"] ) @limiter.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' def on_post(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_429 # but a different endpoint can still be hit r = client.simulate_post('/things') assert r.status == HTTP_200
def test_get_remote_addr(): """ Test using the get_remote_addr() key function as default """ limiter = Limiter( key_func=get_remote_addr, default_limits=["10 per hour", "1 per second"] ) @limiter.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def test_default_limit(limiter): """ Test the default limit applied through the class decorator """ @limiter.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' def some_other_method(self): pass app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def test_limit_on_method_overwrite(limiter): """ Test the limit decorator on the method overwriting the default limit """ class ThingsResource: # the default limit on 'limiter' is 1 per second @limiter.limit(limits="2 per second") def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def test_no_limit(limiter): """ Test a no limit resource even when another resource has a limit """ @limiter.limit() class ThingsResource: def on_get(self, req, resp): resp.body = 'Hello world!' class ThingsResourceNoLimit: def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) app.add_route('/thingsnolimit', ThingsResourceNoLimit()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 # due to the 1 second default limit on the default limiter r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200 for i in range(3): r = client.simulate_get('/thingsnolimit') assert r.status == HTTP_200
def test_limit_class_and_method(limiter): """ Class level decorator gets overwritten by the method level one """ # the default limit on 'limiter' is 1 per second @limiter.limit(limits="5 per hour;2 per second") class ThingsResource: # also the limits are in an unusual order: @limiter.limit(limits="3 per second;5 per hour") def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def test_limit_w_semicol(limiter): """ The limit might be provided semicol-separated """ class ThingsResource: # the default limit on 'limiter' is 1 per second @limiter.limit(limits="5 per hour;2 per second") def on_get(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things') assert r.status == HTTP_200
def hydrated_client_multiple_middleware(): """A Falcon API with an endpoint for testing Marshmallow, with all included middlewares registered.""" data_store = DataStore() class PhilosopherResource: schema = Philosopher() def on_get(self, req, resp, phil_id): """Get a philosopher""" req.context['result'] = data_store.get(phil_id) class PhilosopherCollection: schema = Philosopher() def on_post(self, req, resp): req.context['result'] = data_store.insert(req.context['json']) app = API(middleware=[ m.Marshmallow(), m.JSONEnforcer(), m.EmptyRequestDropper()]) app.add_route('/philosophers', PhilosopherCollection()) app.add_route('/philosophers/{phil_id}', PhilosopherResource()) yield testing.TestClient(app) data_store.clear()
def api(title, version): log = Log.get('api') middlewares = [Negotiation_Middleware()] if Arg_Reader.db.auth: log.notice('JWT authentication enabled') middlewares.append( Falcon_Auth_Middleware( JWT_Auth_Backend( user_loader=lambda token: {'user': token}, secret_key=Arg_Reader.db.auth_secret_key, auth_header_prefix=Arg_Reader.db.auth_header_prefix), exempt_routes=['/api/doc', '/api/doc/swagger.json'])) else: log.notice('JWT authentication disabled') if Arg_Reader.db.https: log.notice('Force to use HTTPS instead of HTTP') middlewares.append(RequireHTTPS()) else: log.notice('HTTPS not set') if Arg_Reader.db.apm_enabled: log.notice('Elastic APM enabled') middlewares.append( Elastic_Apm_Middleware(service_name='lcp-apm', server_url=Arg_Reader.db.apm_server)) else: log.notice('Elastic APM disabled') instance = API(middleware=middlewares) media_handlers = { falcon.MEDIA_JSON: JSON_Handler(loads=loads, dumps=partial(dumps, ensure_ascii=False, sort_keys=True)), falcon.MEDIA_MSGPACK: Message_Pack_Handler(), falcon.MEDIA_XML: XML_Handler(), falcon.MEDIA_YAML: YAML_Handler() } instance.req_options.media_handlers.update(media_handlers) instance.resp_options.media_handlers.update(media_handlers) instance.add_error_handler(*Bad_Request_Handler.get()) instance.add_error_handler(*Internal_Server_Error_Handler.get()) instance.add_error_handler(*Unsupported_Media_Type_Handler.get()) api_spec = Spec(api=instance, title=title, version=version) routes(api=instance, spec=api_spec.get()) falcon_api_doc(instance, config_path='./swagger/schema.json', url_prefix='/api/doc', title='API doc') api_spec.write() return instance
def test_simulate_request_http_version(version, valid): app = API() if valid: testing.simulate_request(app, http_version=version) else: with pytest.raises(ValueError): testing.simulate_request(app, http_version=version)
def test_dynamic_limits_on_method2(): """ Test using the dynamic_limits param of the method decorators to change the limit per user Overwriting the default limits from the class level decortor """ limiter = Limiter(key_func=get_remote_addr, default_limits=["10 per hour", "1 per second"]) @limiter.limit() class ThingsResource: @limiter.limit(dynamic_limits=lambda req, resp, resource, req_succeeded: '5/second' if req.get_header('APIUSER') == 'admin' else '2/second') def on_get(self, req, resp): resp.body = 'Hello world!' def on_post(self, req, resp): resp.body = 'Hello world!' app = API(middleware=limiter.middleware) app.add_route('/things', ThingsResource()) client = testing.TestClient(app) #### # 'normal' user - errors after more than 2 calls per sec r = client.simulate_get('/things') assert r.status == HTTP_200 r = client.simulate_get('/things') assert r.status == HTTP_200 # due to the 2/second limit r = client.simulate_get('/things') assert r.status == HTTP_429 ######### # 'admin' user should be able to make 5 calls admin_header = {"APIUSER": "******"} for i in range(5): r = client.simulate_get('/things', headers=admin_header) assert r.status == HTTP_200 # at the 6th hit even the admin user will error: r = client.simulate_get('/things', headers=admin_header) assert r.status == HTTP_429 sleep(1) r = client.simulate_get('/things', headers=admin_header) assert r.status == HTTP_200 ######## # the on_post() method gets the default limit - 1 per second r = client.simulate_post('/things', headers=admin_header) assert r.status == HTTP_200 r = client.simulate_post('/things', headers=admin_header) assert r.status == HTTP_429
def test_caching_multiple_decorators_on_method(caches, eviction_strategy): """ Testing caching when there are multiple decorators on the method """ # get the cache for the given eviction strategy cache = caches[eviction_strategy] class CachedResource: # a resource where the cache is the first decorator @cache.cached(timeout=1) @a_decorator def on_get(self, req, resp): if FALCONVERSION_MAIN < 3: resp.body = json.dumps({'num': random.randrange(0, 100000)}) else: resp.text = json.dumps({'num': random.randrange(0, 100000)}) class CachedResource2: # a resource where the cache is NOT the first decorator @a_decorator @cache.cached(timeout=1) def on_get(self, req, resp): if FALCONVERSION_MAIN < 3: resp.body = json.dumps({'num': random.randrange(0, 100000)}) else: resp.text = json.dumps({'num': random.randrange(0, 100000)}) class CachedResource3: # a resource where the cache is NOT the first decorator, but the register() is used @register(a_decorator, cache.cached(timeout=1)) def on_get(self, req, resp): if FALCONVERSION_MAIN < 3: resp.body = json.dumps({'num': random.randrange(0, 100000)}) else: resp.text = json.dumps({'num': random.randrange(0, 100000)}) app = API(middleware=cache.middleware) app.add_route('/randrange_cached', CachedResource()) app.add_route('/randrange_cached2', CachedResource2()) app.add_route('/randrange_cached3', CachedResource3()) client = testing.TestClient(app) # scenario 1 - the cache is the first decorator result1 = client.simulate_get('/randrange_cached') result2 = client.simulate_get('/randrange_cached') assert result1.json['num'] == result2.json['num'] # scenario 2 - the cache is NOT the first decorator - caching does NOT work! result1 = client.simulate_get('/randrange_cached2') result2 = client.simulate_get('/randrange_cached2') assert result1.json['num'] != result2.json['num'] # scenario 3 - the cache is the NOT first decorator, but register() is used, so caching does work! result1 = client.simulate_get('/randrange_cached3') result2 = client.simulate_get('/randrange_cached3') assert result1.json['num'] == result2.json['num']
def create_api(model_container=None): api = API() if not model_container: # model_container instance model_container = ModelContainer() # handling requests api.add_route('/predict', PredictController(model_container)) api.add_route('/info', InfoController()) return api
def create_client(uri_template, resource, handlers=None): app = API() app.add_route(uri_template, resource) if handlers: app.req_options.media_handlers.update(handlers) client = testing.TestClient(app) client.resource = resource return client
def test_content_type_json_required(self, endpoint, method, endpoint_exists, ct_is_json, required): # type: (str, str, bool, bool, bool) -> None """Test HTTP methods that should reuquire content-type json Note ``required`` is always true at the moment, because the middleware applies to all requests with no exceptions. If that changes, add parameters above. The endpoint here does not really matter, because ``process_request`` middleware, which handles enforcement of the accept header, is processed before routing. However, the ``endpoint_exists`` option is provided in case it is necessary in the future to ensure this is called against a real endpoint. """ client = testing.TestClient(API(middleware=[m.JSONEnforcer()])) if ct_is_json: heads = {'Content-Type': str('application/json')} else: heads = {} res = client.simulate_request( method=str(method), path=endpoint, headers=heads ) # type: testing.Result meth_not_allowed = status_codes.HTTP_METHOD_NOT_ALLOWED if required: if method in m.JSON_CONTENT_REQUIRED_METHODS: # Ensure that non-json content type is denied if ct_is_json: if endpoint_exists: assert bool( 200 <= res.status_code < 300 or res.status_code == meth_not_allowed ) else: assert res.status == status_codes.HTTP_NOT_FOUND else: exp_code = status_codes.HTTP_UNSUPPORTED_MEDIA_TYPE assert res.status == exp_code else: # We shouldn't deny for non-json content type if endpoint_exists: assert bool( 200 <= res.status_code < 300 or res.status_code == meth_not_allowed ) else: assert res.status == status_codes.HTTP_NOT_FOUND else: # No endpoints for which this is applicable exist at the moment pass
def create_app(settings_file): configuration = Configuration.load_from_py(settings_file) app = API() _setup_logging(configuration) load_resources(configuration, app) configure_metrics_from_dict(configuration) return app
def app(environment=None): """ Create our echo falcon app. """ if environment is None: environment = os.environ logging.basicConfig() logging.getLogger("falcon").setLevel(logging.DEBUG) api = API() api.add_route("/ping", PingResource()) api.add_sink(sink(environment)) return api
def create_client(resource=None, middleware=None, handlers=None): res = resource or SimpleTestResource() app = API(middleware=middleware) app.add_route('/', res) if handlers: app.req_options.media_handlers.update(handlers) client = TestClient(app) client.resource = res return client
def __build_api(db: Session) -> API: api = API( middleware=[ RequireJSON(), JSONTranslator(), FalconAuthMiddleware( JwtAuth(default_jwt_config, db).get_backend(), [], ['HEAD'] ) ] ) api.req_options.auto_parse_form_urlencoded = True return api
def setUp(self): super().setUp() self.api = API(middleware=self.auth_middleware) self.api.add_route(self.route, ExampleResource()) self.auth_storage.clear() identity = [self.user[key] for key in self.ident_keys] self.auth_storage.register( self.auth_middleware[0], identity[0] if len(identity) == 1 else identity, self.user )
def main(): global app specfile = "./apis/api.yaml" specurl = "file://" + abspath(specfile) specdict = read_yaml_file(specfile) openapi_spec = create_spec(specdict, spec_url=specurl) openapi_middleware = FalconOpenAPIMiddleware.from_spec(openapi_spec) app = API(middleware=[openapi_middleware]) auth_server = Auth() user_server = User() app.add_route('/user', user_server) app.add_route('/token', auth_server)