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
...
Related
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.
I'm writing a python REST client for an API.
The API needs authentication and I would like to have many API client objects running on the same script.
My current code for the API is something like this:
class RestAPI:
def __init__(self, id):
self.id = id
self.fetch()
def fetch(self):
requests.get(self.url+self.id, auth=self.apikey)
class Purchase(RestAPI):
url = 'http://example.com/purchases/'
class Invoice(RestAPI):
url = 'http://example.com/invoices/'
...
And I would like to use the API like that:
api_admin = Api('adminmytoken')
api_user = Api('usertoken')
…
amount = api_admin.Purchase(2).amount
api_user.Purchase(2).amount # raises because api_user is not authorized for this purchase
The problem is that each object needs to know it's apikey depending on the client I want to use.
That pattern looks like to me to a "class factory": all the classes of RestAPI need to know of the provided token.
How is it possible to cleanly do that without giving manually the token to each model ?
I think the issue here is that your design is a little backwards. Inheritance might not be the key here. What I might do is take the api token as an argument on the User class, then that gets passed to an instance-level binding on the Rest interface:
class APIUser:
def __init__(self, id, api_key, **kwargs):
self._rest = Interface(id, api_key, **kwargs)
def purchase(self, some_arg):
# the interface itself does the actual legwork,
# and you are simply using APIUser to call functions with the interface
return self._rest.fetch('PURCHASE', some_arg)
class Interface:
methods = {
# call you want (class url)
'PURCHASE': (Purchase, 'https://myexample.com/purchases'),
'INVOICE': (Invoice, 'https://myexample.com/invoices'),
# add more methods here
}
def __init__(self, id, key):
self.id = id
self.key = key
self.session = requests.Session()
def _fetch(self, method, *args, **kwargs):
# do some methods to go get data
try:
# use the interface to look up your class objects
# which you may or may not need
_class, url = self.methods[method]
except KeyError as e:
raise ValueError(f"Got unsupported method, expected "
f"{'\n'.join(self.methods)}") from e
headers = kwargs.pop('headers', {})
# I'm not sure the actual interface here, maybe you call the
# url to get metadata to populate the class with first...
req = requests.Request(_class.http_method, url+self.id, auth=self.key, headers=headers).prepare()
resp = self.session.send(req)
# this will raise the 401 ahead of time
resp.raise_for_status()
# maybe your object uses metadata from the response
params = resp.json()
# return the business object only if the user should see it
return _class(*args, **kwargs, **params)
class Purchase:
http_method = 'GET'
def __init__(self, *args, **kwargs):
# do some setup here with your params passed by the json
# from the api
user = APIUser("token", "key") # this is my user session
some_purchase = user.purchase(2) # will raise a 401 Unauthorized error from the requests session
admin = APIUser("admintoken", "adminkey") # admin session
some_purchase = admin.purchase(2)
# returns a purchase object
some_purchase.amount
There are a few reasons why you might want to go this way:
You don't get the object back if you aren't allowed to see it
Now the rest interface is in control of who sees what, and that's implicitly tied to the user object itself, without every other class needing to be aware of what's going on
You can change your url's in one place (if you need to)
Your business objects are just business objects, they don't need to do anything else
By separating out what your objects actually are, you still only need to pass the api keys and tokens once, to the User class. The Interface is bound on the instance, still giving you the flexibility of multiple users within the same script.
You also get the models you call on explicitly. If you try to take a model, you have to call it, and that's when the Interface can enforce your authentication. You no longer need your authentication to be enforced by your business objects
Is there a way to current_user in a custom field validator. It's almost as if the validator is instantiated at system set up time when there are no users rather then during the session call.
Thanks!
I haven't found a way to set validators at runtime, but there is a way to define a validation function for a field on a form class, and that function can be based on data passed to form at construction time. Here's an example
class MyForm(flask_wtf.FlaskForm):
user_id_field = wtforms.StringField("User ID")
def __init__(self, valid_user_id, **kwargs):
super().__init__(**kwargs)
self.valid_user_id = valid_user_id
def validate_user_id_field(self, field):
"""
Function name must be 'validate_' + name of field you want to validate
"""
if field.data != self.valid_user_id
raise wtforms.validators.ValidationError("Wrong user id!")
# In endpoint definition:
my_form = MyForm(formdata=flask.request.form, valid_user_id=flask_login.current_user.id)
my_form.validate()
Edit
Ha, turns out setting validators at runtime is incredibly easy, it's just that documentation doesn't mention it anywhere.
If you have a form with field some_field, you can simply set self.some_field.validators = [...] in form's constructor, e.g.
class MyForm(flask_wtf.FlaskForm):
some_field = wtforms.HiddenField()
def __init__(self, some_data, **kwargs):
super().__init__(**kwargs)
self.some_field.validators = [MyValidator(some_data)]
# In endpoint
form = MyForm(formdata=flask.request.form, some_data=some_data)
Or you can set validators directly in endpoint handler:
form = MyForm(formdata=flask.request.form)
form.some_field.validators = [MyValidator(some_data)]
Have you tried doing something like the below? I don't know if it works but it allows you to define a static class at runtime and pass the current_user as an argument which the class inherits from global scope and doesnt mutate.
def form_generator(current_user):
class web_form:
user_email = StringFiled('Email', validators=[Email()])
def validate_user_email(self, field)
if Model.query.filter(field.data == current_user.email).first()
return
else:
return ValidationError("Email is not current user's")
return web_form()
In your flask route then try:
form = form_generator(current_user)
Sorry this is untested, i just saw it and posted a response. But if you try it let me know if it succeeds, or more interestingly why it failed!!
While adding Validators at runtime is possible, it is not a good solution. The validator would have to be added after any initialization of the form!
In my opinion a better solution is to simply use a local import:
class ValidateIfUserMayDoThis:
def __init__(self, message):
self.message
def __call__(self, form, field):
from flask_login import current_user # DO THE IMPORT HERE!!!!
if current_user.username in ['Donald']:
raise ValidationError(self.message)
I think my problem is related on the way I structured my pyramid project.
What I want to accomplish is to make my code runs on all views, I don't want to paste same codes on all views. Its like I will include the code in all views by just simply calling it. This is my code.
my wizard module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
import json
class WizardView:
def __init__(self, request):
self.request = request
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
my bill module
from pyramid.view import view_config, view_defaults
from .models import *
from datetime import datetime
from pyramid.response import Response
from bson import ObjectId
from pyramid.httpexceptions import HTTPFound
class BillView:
def __init__(self, request):
self.request = request
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
As you can see I have to paste this code twice (this code checks if session exist, if not, then redirect user to login page)
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
I've tried to search and I think what I need is some sort of auto loader? How can I apply this on pyramid? Or should I stick with this process?
If you wish to return exactly the same thing from both (many) views, then the best way is to use inheritance.
class GenericView:
def __init__(self, request):
self.request = request
def generic_response(self):
session = self.request.session
if session:
return {'fullname':session['name'],'userrole':session['userrole']}
else:
url = self.request.route_url('login')
return HTTPFound(location=url)
Use generic_response in WizardView and BillView
class WizardView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
return self.generic_response()
class BillView(GenericView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
return self.generic_response()
If you want to just check (and redirect) when session does not exists and otherwise proceed normally you could use custom exceptions and corresponding views.
First define custom exception
class SessionNotPresent(Exception):
pass
And view for this exception
#view_config(context=SessionNotPresent)
def handle_no_session(context, request):
# context is our custom exception, request is normal request
request.route_url('login')
return HTTPFound(location=url)
Then just check if session exists in parent constructor
class SessionView:
def __init__(self, request):
self.request = request
if not self.request.session:
raise SessionNotPresent() # Pyramid will delegate request handilng to handle_no_request
In views simply extend SessionView and non existing sessions will be handeled by handle_no_session.
class WizardView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do wizard specific initialization
#view_config(route_name='wizard', renderer='templates/wizard.jinja2')
def wizard(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
class BillView(SessionView):
def __init__(self, request):
super().__init__(request)
# Do bill specific initialization
#view_config(route_name='bills', renderer='templates/bills.jinja2')
def bills(self):
session = self.request.session
return {'fullname':session['name'],'userrole':session['userrole']}
You could easily add additional parameters to exception (redirect_url, ...)
For exception handling see http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/pylons/exceptions.html
You can use events: http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/events.html
Example:
from pyramid.events import subscriber, NewRequest
#subscriber(NewRequest)
def check_session(event):
if not event.request.session:
raise HTTPFound(location=event.request.route_path('login'))
Is there any way to access current user object or at least id without access to request? Let's say in model?
I use pyramid and SQLAlchemy.
First I thought about Singleton, but I think this may return wrong object when there is more users.
class UserSingleton(object):
user = None
def __new__(cls, user=None):
UserSingleton.user = user
return UserSingleton.user
#classmethod
def get(cls):
return cls.user
I need this to use with my model, but I can't pass request in here, because this is called by orm event:
#classmethod
def log(cls, description, log_type=None, name=None):
user = ??
audit_log = cls(
user_id=user.id if user else None,
description=description,
type=log_type,
name=name
)
DBSession.add(audit_log)
return audit_log
Ok, found a solution by doing:
from pyramid.threadlocal import get_current_request
request = get_current_request()