Apply Flask logging middleware to only one view - python

I have the following middleware right now:
class LoggingMiddleware(object):
def __init__(self, app):
self._app = app
def __call__(self, environ, resp):
keys = ['HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING',
'HTTP_X_FORWARDED_FOR', 'HTTP_REFERER',
'HTTP_USER_AGENT', 'PATH_INFO',
'QUERY_STRING', 'REMOTE_ADDR']
dumpable = { k:environ.get(k, None) for k in keys }
print json.dumps(dumpable) # Not sure how to get this to work with papertrail
return self._app(environ, resp)
which I install via:
app.wsgi_app = LoggingMiddleware(app.wsgi_app)
This logs every single request handled by my application. I would like to limit this scope only to one view function,
#app.route('/foo/')
def foo
How can I do so?

If you don't need the unmodified environment keys, just use a decorator for that particular route and get environ off of request.environ:
def log_request(route):
#functools.wraps(route)
def wrapper(*args, **kwargs):
keys = ['HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING',
'HTTP_X_FORWARDED_FOR', 'HTTP_REFERER',
'HTTP_USER_AGENT', 'PATH_INFO',
'QUERY_STRING', 'REMOTE_ADDR']
dumpable = { k:environ.get(k, None) for k in keys }
# TODO: Log elsewhere
print(json.dumps(dumpable))
return route(*args, **kwargs)
return wrapper
Then just wrap the one route you care about in a logging decorator:
#app.route('/foo/')
#log_request
def foo():
return 'hello from /foo/'
Alternatively, if you must use a middleware (or if you want to configure which routes get logged at run time, rather than build time), you can just look at PATH_INFO and log only if the path matches:
if environ.get('PATH_INFO') in ('/foo/', '/bar'):
keys = # ... snip ...
return self._app(environ, resp)

Related

Spyne, Django change WSDL url

I am using django behind nginx reverse proxy and django sees the server url different than what it actually is hosted on like:
Django: http://webserver.com
Nginx: https://webserver.com
When I try to add the WSDL to SoapUI it automatically defaults to the first http://webserver.com server and then all the requests fail. I have tried the code below, but it did not work:
...
app = Application(
[EXTWS],
tns='soap.views',
in_protocol=Soap11(validator='soft'),
out_protocol=Soap11(),
)
app.transport = "no_transport_at_all"
...
wsdl = Wsdl11(app.interface)
if os.environ.get("DEBUG"):
wsdl.build_interface_document('http://localhost:8000/wsdl/')
else:
url = f'https://{settings.DOMAIN}/wsdl/'
wsdl.build_interface_document(url)
Inspirations: here and here
EDIT:
Looks like the code above achieves some things but the resulting WSDL document when accessed in browser is still the same, maybe it is generated on request; the documentation said "... Spyne will get the URL from the first request, build the wsdl on-the-fly and cache it as a string in memory for later requests." but here it is generated manually, so it should not generate a new one maybe? Or it is generating it by request because it is django, not wsgi.
EDIT:
Looks like building the tree by hand does not make any difference as when you send the first requests, a new instance of Wsdl11 class is generated.
Temporarily, I achieved changing the url to what I want by basically monkey patching the two classes as follows:
from functools import update_wrapper
from spyne.server.http import HttpBase, HttpMethodContext, HttpTransportContext
from spyne.application import get_fault_string_from_exception, Application
from django.http import HttpResponse, HttpResponseNotAllowed, Http404
class MonkeyDjangoServer(DjangoServer):
def handle_wsdl(self, request, *args, **kwargs):
"""Return services WSDL."""
ctx = HttpMethodContext(self, request,
'text/xml; charset=utf-8')
if self.doc.wsdl11 is None:
raise Http404('WSDL is not available')
if self._wsdl is None:
# Interface document building is not thread safe so we don't use
# server interface document shared between threads. Instead we
# create and build interface documents in current thread. This
# section can be safely repeated in another concurrent thread.
self.doc.wsdl11.service_elt_dict = {}
# here you can put whatever you want
self.doc.wsdl11.build_interface_document("http://MONKEY/")
wsdl = self.doc.wsdl11.get_interface_document()
if self._cache_wsdl:
self._wsdl = wsdl
else:
wsdl = self._wsdl
ctx.transport.wsdl = wsdl
response = HttpResponse(ctx.transport.wsdl)
return self.response(response, ctx, ())
class MonkeyDjangoView(DjangoView):
#classmethod
def as_view(cls, **initkwargs):
"""Register application, server and create new view.
:returns: callable view function
"""
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__,
key))
def get(key):
value = initkwargs.get(key)
return value if value is not None else getattr(cls, key)
def pop(key):
value = initkwargs.pop(key, None)
return value if value is not None else getattr(cls, key)
application = get('application') or Application(
services=get('services'),
tns=get('tns'),
name=get('name'),
in_protocol=get('in_protocol'),
out_protocol=get('out_protocol'),
)
server = pop('server') or MonkeyDjangoServer(application,
chunked=get('chunked'),
cache_wsdl=get('cache_wsdl'))
def view(request, *args, **kwargs):
self = cls(server=server, **initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
urlpatterns = [
url(r'^EXTWS/$', MonkeyDjangoView.as_view(application=app)),
# url(r'^schema/$', get_schema),
]
This is some unacceptable kind of solution so I will be waiting for a logical implementation of this behavior. Until then, I will be using this.

Access view configuration from renderer

Is there a way to access the view configuration from a renderer? By view configuration I mean the arguments passed to the view_config() decorator. My goal is to add some settings to the view configuration which a renderer can then use.
I have a custom renderer:
class MyRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
# Get view options.
my_renderer_opts = ...
# Render using options.
...
which is registered as:
config.add_renderer('my_renderer', MyRenderer)
Then in my view I have:
class Page(object):
def __init__(self, request):
self.request = request
#pyramid.view.view_config(
route_name='root',
renderer='my_renderer',
my_renderer_opts={...}
)
def view(self):
pass
Is there a way to access my_renderer_opts passed to view_config() from MyRenderer.__call__()?
if you still want implement it as described, maybe deriver will be helpfull:
from wsgiref.simple_server import make_server
from pyramid.view import view_config
from pyramid.config import Configurator
#view_config(route_name="hello", renderer="myrend", renderer_options={"a": 1})
def hello_world(request):
return "Hello World!"
def rendereropt_deriver(view, info):
options = info.options.get("renderer_options", {})
def wrapped(context, request):
setattr(request, "_renderer_options", options)
return view(context, request)
return wrapped
rendereropt_deriver.options = ("renderer_options",)
class MyRendererFactory:
def __init__(self, info):
self.info = info
def __call__(self, value, system):
options = getattr(system["request"], "_renderer_options", {})
print("`renderer_options` is {}".format(options))
return value
if __name__ == "__main__":
with Configurator() as config:
config.add_route("hello", "/")
config.add_view_deriver(rendereropt_deriver)
config.add_renderer("myrend", MyRendererFactory)
config.scan(".")
app = config.make_wsgi_app()
server = make_server("0.0.0.0", 8000, app)
server.serve_forever()

Pass objects from server interceptors to functions

I created a simple Server Interceptor that retrieves the user based on the JWT token.
But now I would like to make it available to all the methods of my services.
At the moment im using decorators. But I would like to avoid having to decorate all the methods. In case, decorate only those that do not need the user.
Some one can give me a clue ?
here is my code:
class AuthInterceptor(grpc.ServerInterceptor):
"""Authorization Interceptor"""
def __init__(self, loader):
self._loader = loader
def intercept_service(self, continuation, handler_call_details):
# Authenticate if we not requesting token.
if not handler_call_details.method.endswith('GetToken'):
# My Authentication class.
auth = EosJWTAuth()
# Authenticate using the headers tokens to get the user.
user = auth.authenticate(
dict(handler_call_details.invocation_metadata))[0]
# Do something here to pass the authenticated user to the functions.
cont = continuation(handler_call_details)
return cont
And I'd like my methods can to access the user in a way like this.
class UserService(BaseService, users_pb2_grpc.UserServicer):
"""User service."""
def get_age(self, request, context):
"""Get user's age"""
user = context.get_user()
# or user = context.user
# or user = self.user
# os user = request.get_user()
return pb.UserInfo(name=user.name, age=user.age)
This is a common need for web servers, and it is a good idea to add decorators to the handlers to explicitly set requirement for authentication/authorization. It helps readability, and reduces the overall complexity.
However, here is a workaround to solve your question. It uses Python metaclass to automatically decorate every servicer method.
import grpc
import functools
import six
def auth_decorator(func):
#functools.wraps(func)
def wrapper(request, context):
if not func.__name__.endswith('GetToken'):
auth = FooAuthClass()
try:
user = auth.authenticate(
dict(context.invocation_metadata)
)[0]
request.user = user
except UserNotFound:
context.abort(
grpc.StatusCode.UNAUTHENTICATED,
'Permission denied.',
)
return func(request, context)
return wrapper
class AuthMeta:
def __new__(self, class_name, bases, namespace):
for key, value in list(namespace.items()):
if callable(value):
namespace[key] = auth_decorator(value)
return type.__new__(self, class_name, bases, namespace)
class BusinessServer(FooServicer, six.with_metaclass(AuthMeta)):
def LogicA(self, request, context):
# request.user accessible
...
def LogicGetToken(self, request, context):
# request.user not accessible
...

unit test for decorator in a python package by patching flask request

I have a python module security.py which defines a decorator authorized().
I want to test the decorator. The decorator will receive a flask request header.
The decorator is sth like this:
def authorized():
def _authorized(wrapped_func):
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
return _authorized
I want to mock the flask request header using a #patch decorator.The test I wrote is sth like this:
#patch('security.request.headers', Mock(side_effect=lambda *args, **kwargs: MockHeaders({})))
def test_no_authorization_token_in_header(self):
#security.authorized()
def decorated_func(token='abc'):
return access_token
result = decorated_func()
self.assertEqual(result, None)
class MockHeaders(object):
def __init__(self, json_data):
self.json_data=json_data
but I always get the following error:
name = 'request'
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
How should I do it right?
Mock the whole request object to avoid triggering the context lookup:
#patch('security.request')
and build up the mock from there:
#patch('security.request')
def test_no_authorization_token_in_header(self, mock_request):
mock_request.headers= {}
#security.authorized()
def decorated_func(token='abc'):
return token
self.assertRaises(Abort):
result = decorated_func()
Since a missing token results in an Abort exception being raised, you should explicitly test for that. Note that the request.headers attribute is not called anywhere, so side_effect or return_value attributes don't apply here.
I ignored MockHeaders altogether; your decorator is not using json_data and your implementation is lacking a __contains__ method, so in tests wouldn't work on that. A plain dictionary suffices for the current code-under-test.
Side note: authorized is a decorator factory, but it doesn't take any parameters. It'd be clearer if you didn't use a factory there at all. You should also use functools.wraps() to ensure that any metadata other decorators add are properly propagated:
from functools import wraps
def authorized(wrapped_func):
#wraps(wrapped_func)
def _wrap(*args, **kwargs):
if 'token' not in request.headers:
LOG.warning("warning")
abort(401)
return None
return wrapped_func(*args, **kwargs)
return _wrap
then use the decorator directly (so no call):
#security.authorized
def decorated_func(token='abc'):
return access_token

Unit-testing a flask-principal application

All, I'm writing a flask application that depends on flask-principal for managing user roles. I'd like to write some simple unit tests to check which views can be accessed by which user. An example of code is posted on pastebin to avoid cluttering this post. In short, I define a few routes, decorating some so that they can be accessed only by users with the proper role, then try to access them in a test.
In the code pasted, the test_member and test_admin_b both fail, complaining about a PermissionDenied. Obviously, I'm failing to declare the user properly; at least, the info about the user roles is not in the right context.
Any help or insight about the complexities of context processing will be deeply appreciated.
Flask-Principal does not store information for you between requests. It's up to you to do this however you like. Keep that in mind and think about your tests for a moment. You call the test_request_context method in the setUpClass method. This creates a new request context. You are also making test client calls with self.client.get(..) in your tests. These calls create additional request contexts that are not shared between each other. Thus, your calls to identity_changed.send(..) do not happen with the context of the requests that are checking for permissions. I've gone ahead and edited your code to make the tests pass in hopes that it will help you understand. Pay special attention to the before_request filter I added in the create_app method.
import hmac
import unittest
from functools import wraps
from hashlib import sha1
import flask
from flask.ext.principal import Principal, Permission, RoleNeed, Identity, \
identity_changed, identity_loaded current_app
def roles_required(*roles):
"""Decorator which specifies that a user must have all the specified roles.
Example::
#app.route('/dashboard')
#roles_required('admin', 'editor')
def dashboard():
return 'Dashboard'
The current user must have both the `admin` role and `editor` role in order
to view the page.
:param args: The required roles.
Source: https://github.com/mattupstate/flask-security/
"""
def wrapper(fn):
#wraps(fn)
def decorated_view(*args, **kwargs):
perms = [Permission(RoleNeed(role)) for role in roles]
for perm in perms:
if not perm.can():
# return _get_unauthorized_view()
flask.abort(403)
return fn(*args, **kwargs)
return decorated_view
return wrapper
def roles_accepted(*roles):
"""Decorator which specifies that a user must have at least one of the
specified roles. Example::
#app.route('/create_post')
#roles_accepted('editor', 'author')
def create_post():
return 'Create Post'
The current user must have either the `editor` role or `author` role in
order to view the page.
:param args: The possible roles.
"""
def wrapper(fn):
#wraps(fn)
def decorated_view(*args, **kwargs):
perm = Permission(*[RoleNeed(role) for role in roles])
if perm.can():
return fn(*args, **kwargs)
flask.abort(403)
return decorated_view
return wrapper
def _on_principal_init(sender, identity):
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret', TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
#app.before_request
def determine_identity():
# This is where you get your user authentication information. This can
# be done many ways. For instance, you can store user information in the
# session from previous login mechanism, or look for authentication
# details in HTTP headers, the querystring, etc...
identity_changed.send(current_app._get_current_object(), identity=Identity('admin'))
#app.route('/')
def index():
return "OK"
#app.route('/member')
#roles_accepted('admin', 'member')
def role_needed():
return "OK"
#app.route('/admin')
#roles_required('admin')
def connect_admin():
return "OK"
#app.route('/admin_b')
#admin_permission.require()
def connect_admin_alt():
return "OK"
return app
admin_permission = Permission(RoleNeed('admin'))
class WorkshopTest(unittest.TestCase):
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
def test_basic(self):
r = self.client.get('/')
self.assertEqual(r.data, "OK")
def test_member(self):
r = self.client.get('/member')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
def test_admin_b(self):
r = self.client.get('/admin_b')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
if __name__ == '__main__':
unittest.main()
As Matt explained, it's only a matter of context. Thanks to his explanations, I came with two different ways to switch identities during unit tests.
Before all, let's modify a bit the application creation:
def _on_principal_init(sender, identity):
"Sets the roles for the 'admin' and 'member' identities"
if identity.id:
if identity.id == 'admin':
identity.provides.add(RoleNeed('admin'))
identity.provides.add(RoleNeed('member'))
def create_app():
app = flask.Flask(__name__)
app.debug = True
app.config.update(SECRET_KEY='secret',
TESTING=True)
principal = Principal(app)
identity_loaded.connect(_on_principal_init)
#
#app.route('/')
def index():
return "OK"
#
#app.route('/member')
#roles_accepted('admin', 'member')
def role_needed():
return "OK"
#
#app.route('/admin')
#roles_required('admin')
def connect_admin():
return "OK"
# Using `flask.ext.principal` `Permission.require`...
# ... instead of Matt's decorators
#app.route('/admin_alt')
#admin_permission.require()
def connect_admin_alt():
return "OK"
return app
A first possibility is to create a function that loads an identity before each request in our test. The easiest is to declare it in the setUpClass of the test suite after the app is created, using the app.before_request decorator:
class WorkshopTestOne(unittest.TestCase):
#
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
#app.before_request
def get_identity():
idname = flask.request.args.get('idname', '') or None
print "Notifying that we're using '%s'" % idname
identity_changed.send(current_app._get_current_object(),
identity=Identity(idname))
Then, the tests become:
def test_admin(self):
r = self.client.get('/admin')
self.assertEqual(r.status_code, 403)
#
r = self.client.get('/admin', query_string={'idname': "member"})
self.assertEqual(r.status_code, 403)
#
r = self.client.get('/admin', query_string={'idname': "admin"})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
#
def test_admin_alt(self):
try:
r = self.client.get('/admin_alt')
except flask.ext.principal.PermissionDenied:
pass
#
try:
r = self.client.get('/admin_alt', query_string={'idname': "member"})
except flask.ext.principal.PermissionDenied:
pass
#
try:
r = self.client.get('/admin_alt', query_string={'idname': "admin"})
except flask.ext.principal.PermissionDenied:
raise
self.assertEqual(r.data, "OK")
(Incidentally, the very last test shows that Matt's decorator are far easier to use....)
A second approach uses the test_request_context function with a with ... to create a temporary context. No need to define a function decorated by #app.before_request, just pass the route to test as argument of test_request_context, send the identity_changed signal in the context and use the .full_dispatch_request method
class WorkshopTestTwo(unittest.TestCase):
#
#classmethod
def setUpClass(cls):
app = create_app()
cls.app = app
cls.client = app.test_client()
cls.testing = app.test_request_context
def test_admin(self):
with self.testing("/admin") as c:
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
#
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("member"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 403)
#
with self.testing("/admin") as c:
identity_changed.send(c.app, identity=Identity("admin"))
r = c.app.full_dispatch_request()
self.assertEqual(r.status_code, 200)
self.assertEqual(r.data, "OK")
Along Matt's response, I've created a context manager to make the determine_identity a little cleaner:
#contextmanager
def identity_setter(app, user):
#app.before_request
def determine_identity():
#see http://stackoverflow.com/questions/16712321/unit-testing-a-flask-principal-application for details
identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
determine_identity.remove_after_identity_test = True
try:
yield
finally:
#if there are errors in the code under trest I need this to be run or the addition of the decorator could affect other tests
app.before_request_funcs = {None: [e for e in app.before_request_funcs[None] if not getattr(e,'remove_after_identity_test', False)]}
So when I run my test it looks like:
with identity_setter(self.app,user):
with user_set(self.app, user):
with self.app.test_client() as c:
response = c.get('/orders/' + order.public_key + '/review')
I hope this helps, and I would welcome any feedback :)
~Victor

Categories

Resources