Spyne, Django change WSDL url - python

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.

Related

Python decorator class with members

I want to write a class that will have member variables and member functions that will be used as decorators.
class decorator_class:
def __init__(self, baseurl, username,password):
self._baseurl = baseurl
self._username = _username
self._password = _password
def decorator_get(self,arguments):
def inner_function(function):
#wraps(function)
def wrapper(*args, **kwargs):
url = self._url + argument
if len(kwargs) > 0:
url+="?"
argseperator=""
for k,v in kwargs.items():
url+="{}{}={}".format(argseperator,k,v)
argseperator="&"
r = requests.get(url, auth=(self._username, self._password))
if r.status_code != 200:
raise Exception('Failed to GET URL: {}'.format(url))
return function(args[0],json = r.json())
return wrapper
return inner_function
class usedecorator:
def __init__(baseurl, self,user,password):
self.dec = decorator_class(baseurl, self,user,password)
#dec.decorator_get('/path/to/resource1')
def process_output_resource1(self, json):
do_something_with_json
The problem is that __init__ is being called after the class is loaded and at that time dec is undefined.
if I define the decorator_class globally it works, but then there is no way to pass the url, user and password to it at runtime.
Any suggestions?
Your decorator_get > innder_function > wrapper has the userdecorator's self.
Weird sentence but eh.
You have some weird namings, IDK why did you use self as a second argument for instance but, I tried to follow your naming.
def decorator_get(arguments):
def inner_function(function):
#wraps(function)
def wrapper(self, *args, **kwargs):
url = self._base_url + arguments
if len(kwargs) > 0:
url+="?"
argseperator=""
for k,v in kwargs.items():
url+="{}{}={}".format(argseperator,k,v)
argseperator="&"
r = requests.get(url, auth=(self._username, self._password))
if r.status_code != 200:
raise Exception('Failed to GET URL: {}'.format(url))
return function(self, json = r.json())
return wrapper
return inner_function
class usedecorator:
def __init__(self, baseurl,user,password):
self._base_url = baseurl
self._username = user
self._password= password
#decorator_get('/path/to/resource1')
def process_output_resource1(self, json):
do_something_with_json
Indeed -to have a decorator for methods in a class, it must already be defined (i.e. ready to be used) when the method to be decorated is declared: which means it have to be declared either at top-level or inside the class body.
Code inside methods, including __init__, however will only run when an instance is created - and that is the point where the class will get your connection parameters.
If this decorator is being used always in this model, you can turn it into a descriptor: an object which is a class attribute, but which has code (in a method named __get__) that is executed after the instance is created.
This descriptor could then fetch the connection parameters in the instance itself, after it has been created, and prepare way for calling the underlying method.
That will require some reorganization on your code: the object returned by __get__ has to be a callable which will ultimately run your function, but it would not be nice if simply retrieving the method name would trigger the network request - one will expect it to be triggered when the process_output... method is actually called. The __get__ method then should return your inner "wrapper" function, which will have all the needed data for the request from the "instance" attribute Python passes automatically, but for the payload which it gets via kwargs.
class decorator_class:
def __init__(self, path=None):
self.path = None
self.func = None
def __get__(self, instance, owner):
if instance is None:
return self
def bound_to_request(**kwargs):
# retrieves the baseurl, user and password from the host instance:
# build query part of the target URL - instead of your convoluted
# code to build the query string (which will break ont he first special character,
# just pass kwargs as the "params" argument)
response = requests.get(instance._base_url, auth=(
instance.user, instance.password), params=kwargs)
# error treatment code
#...
return self.func(response.json())
return bound_to_request
def __call__(self, arg):
# create a new instance of this class on each stage:
# first anotate the API path, on the second call annotate the actual method
new_inst = type(self)()
if not self.path:
if not isinstance(arg, str):
raise TypeError("Expecting an API path for this method")
new_inst.path = arg
else:
if not callable(arg):
raise TypeError("Expecting a target method to be decorated")
new_inst.func = wraps(arg)
return new_inst
def __repr__(self):
return f"{self.func.__name__!r} method bound to retrieve data from {self.path!r}"
class use_decorator:
dec = decorator_class()
def __init__(self=, baseurl, user, password):
# the decorator assumes these to be set as instance attributes
self.baseurl = baseurl
self.user = user
self.password = password
# <- the call passing the path returns an instance of
# the decorator with the path set. it is use as an
# decorator is called again, and on this second call, the decorated method is set.
#dec.decorator_get('/path/to/resource1')
def process_output_resource1(self, json):
# do_something_with_json
...
In time, re-reading your opening paragraph, I see you intended to have more than one decorator inside your original class, probably others intended for "POST" and other HTTP requests: most important thing, the __get__ name here has nothing to do with HTTP: it is a fixed method name in the Python spec which is called automatically by the language when one will retrieve your method from an instance of use_decorator. That is, when there is code: my_instance.process_output_resource1(...), the __get__ method of the descriptor is called. Whatever it returned is then called.
For enabling the same decorator to use POST and other HTTP methods, I suggest you to have as a first parameter when annotating the path for each method, and then simply call the appropriate requests method by checking self.method inside the bound_to_request function.
I think you're going too far with the decorator approach. Let's break this down into a single question: What is the actual shared state here that you need a class for? To me, it looks like just the baseurl, user, and password. So let's just use those directly without a decorator:
from requests import Session
from requests.auth import HTTPBasicAuth
class UseDecorator: # this isn't a good name, but we will keep it temporarily
def __init__(self, baseurl, user, password):
self.baseurl = baseurl
self.session = Session()
# we've now bound the authentication to the session
self.session.auth = HTTPBasicAuth(user, password)
# now let's just bind a uri argument to a function to simply
# send a request
def send_request(self, uri, *args, **kwargs):
url = self.baseurl + uri
# you don't need to manually inject parameters, just use
# the params kwarg
r = self.session.get(url, params=kwargs)
# this will check the response code for you and even handle
# a redirect, which your 200 check will fail on
r.raise_for_status()
return r.json()
# then just handle each individual path
def path_1(self, *args, **kwargs):
data = self.send_request('/path/1', *args, **kwargs)
# process data
def path_2(self, *args, **kwargs):
data = self.send_request('/path/2', *args, **kwargs)
# process data
Because we're leveraging the machinery offered to us by requests, most of your decorator is simplified, and we can boil it down to a simple function call for each path

How can I pass the result from a python decorator to my class?

I use tornado and a jwt decorator as below:
def jwtauth(handler_class):
"""
Tornado JWT Auth Decorator
"""
def wrap_execute(handler_execute):
def require_auth(handler, kwargs):
auth = handler.request.headers.get(AUTHORIZATION_HEADER)
if auth:
parts = auth.split()
if not is_valid_header(parts):
return_header_error(handler)
token = parts[1]
try:
result = jwt.decode(
token,
SECRET_KEY,
options=jwt_options
)
except Exception as err:
return_auth_error(handler, str(err))
else:
handler._transforms = []
handler.write(MISSING_AUTHORIZATION_KEY)
handler.finish()
return result
def _execute(self, transforms, *args, **kwargs):
try:
result = require_auth(self, kwargs)
except Exception:
return False
return handler_execute(self, transforms, *args, **kwargs)
return _execute
handler_class._execute = wrap_execute(handler_class._execute)
return handler_class
#jwtauth
class MyHandler(tornado.web.RequestHandler):
def post(self):
unit = json.loads(self.request.body.decode('utf-8'))
# TODO:
# get the result from jwtauth decorator and use it here
print(result) # The result from jwtauth
Now, I'd like to get the jwt decode result and pass into MyHandler for further verification. Can I do it? I checked most of the comment that I can pass the parameter to a decorator but I cannot get from it. Is is possible to pass the jwtauth result to my function?
A class decorator takes your class and spits out a new version of your class (usually with some features added to it). In this case, the #jwtauth decorator takes your class and spits out a new class that makes sure that each request is checked for a valid JWT token in the authorization header. tornado.web.RequestHandler._execute internally calls post. The current behavior is that if the JWT token auth fails, then post will never be called.
In short, you probably want to just raise an error below instead of returning False.
try:
result = require_auth(self, kwargs)
except Exception:
return False
If you need to add more logic about what kind of error to raise, then you probably want to pass that into the decorator along with the class.

Python3 "Class factory" - ex: API(token).MyClass()

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

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
...

Where does Django request object originated in `class View`?

Here is the code segment I am referring to
#classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
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 view(request, *args, **kwargs):
self = cls(**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)
view.view_class = cls
view.view_initkwargs = initkwargs
# 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
I am looking for the code that the request object is passed in to.
The common place where as_view is used is in url
However I couldn't reference to request object in
def url(regex, view, kwargs=None, name=None, prefix=''):
if isinstance(view, (list, tuple)):
# For include(...) processing.
urlconf_module, app_name, namespace = view
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
else:
if isinstance(view, six.string_types):
warnings.warn(
'Support for string view arguments to url() is deprecated and '
'will be removed in Django 1.10 (got %s). Pass the callable '
'instead.' % view,
RemovedInDjango110Warning, stacklevel=2
)
if not view:
raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex)
if prefix:
view = prefix + '.' + view
return RegexURLPattern(regex, view, kwargs, name)
Can someone point me a direction?
Note that the request is never passed to as_view().
The as_view() method is called when the url config is loaded, before any requests are handled. It defines a method view, and returns it.
def view(request, *args, **kwargs):
...
return view
This view method takes an argument request, positional and keyword arguments. The view method is then passed to the url instance. Note that url simply requires a callable that takes a request argument. This could be a callable returned by calling as_view() for a class based view, or a regular function based view, it makes no difference to how the request is passed to the view.
def function_view(request, *args, **kwargs):
return HttpResponse("I'm a function based view")
url(r'^cbv/$', MyView.as_view()),
url(r'^fv/$', function_view),
Then, when a request is handled, the url is resolved into this view, and BaseHandler.get_response calls the view with the request, and args and kwargs captured from the url.
The request is processed by BaseHandler.get_response:
wrapped_callback = self.make_view_atomic(callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs)
...
The request is created from the WSGIHandler class.
James Bennett talks about this in Django In Depth at around 2 hours and 14 minutes. Slides can be found here.

Categories

Resources