Qualified element/attribute forms and unqualified forms with Spyne soap server - soap

Is there any way to use elementFormDefault="unqualified" server schema type with Spyne server?
Now my all trials end up with method response result:
<senv:Envelope xmlns:tns="http://test.com/remoteService/"
xmlns:senv="http://schemas.xmlsoap.org/soap/envelope/">
<senv:Body>
<tns:testResponse>
<tns:status>ok</tns:status>
</tns:testResponse>
</senv:Body>
And generated wsdl fragment with "qualified" elementFormDefault :
<xs:schema targetNamespace="http://test.com/remoteService/" elementFormDefault="qualified"></xs:schema>
How to configure method or parameters model to get result like this:
<senv:Envelope xmlns:tns="http://test.com/remoteService/"
xmlns:senv="http://schemas.xmlsoap.org/soap/envelope/">
<senv:Body>
<tns:testResponse>
<status>ok<status>
</tns:testResponse>
</senv:Body>
My goal is to generate result where child element:
<tns:status>ok</tns:status>
will appear without namespace prefix - like this:
<status>ok<status>

If you wonder how to add listener to the event_manager for method_return_string or for another event, see bellow a full example:
from spyne import Application, rpc, ServiceBase, Iterable, Integer, Unicode
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
class HelloWorldService(ServiceBase):
#rpc(Unicode, Integer, _returns=Iterable(Unicode))
def say_hello(ctx, name, times):
for i in range(times):
yield u'Hello, %s' % name
def on_method_return_string(ctx):
ctx.out_string[0] = ctx.out_string[0].replace(b'Hello>', b'Good by')
HelloWorldService.event_manager.add_listener('method_return_string',
on_method_return_string)
application = Application([HelloWorldService], 'spyne.examples.hello.soap',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11())
wsgi_application = WsgiApplication(application)
if __name__ == '__main__':
import logging
from wsgiref.simple_server import make_server
server = make_server('127.0.0.1', 8000, wsgi_application)
server.serve_forever()
As of Spyne 2.12 this is still the only way to remove namespaces from response variables.

As of 2.10, Spyne does not support this.
The patch would be a bit hairy. Chime in at soap#python.org if you're willing to work on this.
A workaround would be to remove namespace prefixes manually from outgoing documents in a method_return_document hook. If you need to enforce the same for incoming documents as well, you either have to modify the Wsdl as well in a document_built event, or use soft validation (soft validation does not care about namespaces) or no validation at all.

Related

SuiteTalk SOAP API CustomFieldSearch in Java

I am using the Java proxies downloaded from http://content.netsuite.com/download/NSJavaClient2021-2.zip
Given a CustomFieldRef (with say, InternalId "30"), how do I fetch the name of this CustomField? In short, is there a CustomFieldSearch to search all CustomFields?
In the soap world, you'll want to think of the CustomField as the superclass for a number of specialized classes like TransactionBodyCustomField or EntityCustomField. From there, you can access the record using a standard get call. I'm not familiar with the java-proxies, but in python, we'd do something like:
f = soap.get('transactionBodyCustomField', '30')
which would send an xml body with the appropriate recordRef xml.

Different OpenAPI schema in FastAPI depending on environment

We have a FastApi application that is hosted behind a reverse proxy.
The proxy authenticates the user using Kerberos and adds a X-Remote-User HTTP header to the request.
This header is required by the FastApi application. Here is an example route:
#app.get("/user/me")
async def get_user_me(x_remote_user: str = Header(...)):
return {"User": x_remote_user}
The X-Remote-User header is required for the request which is expected behavior.
When we now open the Swagger Ui, the header is documented and when clicking on "Try it out", we can provide the header value.
This behavior is great for development, but in all other cases it is undesired, because that header is provided by the reverse proxy. For instance, we generate clients using OpenAPI Generator and the clients then all require the X-Remote-User parameter in their requests.
Hence, it would be useful to have a configuration that distinguishes between the environments. If we are behind a reverse proxy, then the generated OpenAPI Schema by FastApi should not include the X-Remote-Header, otherwise if we are in development, it should be included.
What I did so far:
I checked the documentation about security and also some source code of these modules, but I was not able to find a solution.
In the documentation, I read the section Behind a Proxy, but nothing there points me to a potential solution.
I also read about Middleware, but again, no solution.
We could change the generated OpenApi schema. I sketched this in my answer below, but this is not a very elegant solution
Does anyone have a good solution to this problem?
We can use APIKeyHeader to remove the X-Remote-User header from the API signature, but still enforcing the header to be present.
from fastapi.security import APIKeyHeader
apiKey = APIKeyHeader(name="X-Remote-User")
#app.get("/user/me")
async def get_user_me(x_remote_user: str = Depends(apiKey)):
return {"User": x_remote_user}
When the header is not present, we get a "403 Forbidden". If it is present, we retrieve the header value.
The Swagger UI now has a button "Authorize" where we can fill-in the value of the X-Remote-User for testing purposes.
One approach is to generate the OpenApi schema as described in the documentation Extending OpenAPI. After the generation, remove the X-Remote-User from the schema. In the configuration could be a flag that the application it is behind a reverse proxy to execute the code conditionally:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from MyConfig import Config
app = FastAPI()
#app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
if Config.reverse_proxy:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
// remove X-Remote-User here
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
However this is not a very elegant solution, as we need to parse the Json string and remove the different deeply-nested occurrences of the X-Remote-User header everywhere. This is prone to bugs resulting in an invalid schema. Furthermore it could break if new Rest endpoints are added.
A new param will be soon available for Header, Query and other to exclude elements from the openAPI output: include_in_schema=False
Example:
def test(x_forwarded_for: str = Header(None, include_in_schema=False)):
...
Here the patch state: https://github.com/tiangolo/fastapi/pull/3144

Creating Soap messages with objectTypes using SUDS library and MessagePlugin in Robot Framework

This a continuation for this questions: Creating Soap messages with objectTypes using SUDS library in Robot Framework
In there it was determined that with RF SUDS it is not possible to create messages with objectTypes using SUDS alone. I would like to try the MessagePlugin approach, but unfortunately the information in documentation is not quite enough for me:
from robot.libraries.BuiltIn import BuiltIn
from suds.plugin import MessagePlugin
class _MyPlugin(MessagePlugin):
def marshalled(self, context):
body = context.envelope.getChild('Body')
foo = body[0]
foo.set('id', '12345')
foo.set('version', '2.0')
class SudsLibraryExtensions(object):
def attach_my_plugin(self):
client = BuiltIn().get_library_instance("SudsLibrary")._client()
# prepend so SudsLibrary's plugin is left in place
plugins = client.options.plugins
if any(isinstance(x, _MyPlugin) for x in plugins):
return
plugins.insert(0, _MyPlugin())
client.set_options(plugins=plugins)
Does anyone have any complete Robot example on how to use the above snippet? What should I be passing into marshalled as context? Do I need to call attach_my_plugin() at some point?
A general description of message plugins can be found in the Suds documentation. More detail is in the class documentation. You do not call marshalled, suds does. To better understand how to implement the marshalled method, read up on the documentation for Element. A suds plugin is essentially a listener. My example uses a public web service for demonstration.
Say your request looks like this:
...
<ns0:Body>
<ns1:GetStatistics>
<ns1:X>
...
But you need it to look like this:
....
<ns0:Body>
<ns1:GetStatistics type="specialType">
<ns1:X>
...
Here is a plugin that adds the type attribute to the GetStatistics element. This may be necessary when an element has child elements and attributes sent. Suds 0.4 does not support this, but it is valid SOAP. There may be a fork of Suds that does support this.
*** Settings ***
Library SudsLibrary
Library c:/SudsLibraryExtensions.py
*** Test Cases ***
Message Plugin
Create Soap Client http://www.webservicex.net/Statistics.asmx?WSDL
Attach My Plugin
Set GetStats Type specialType
${dbl array}= Create Wsdl Object ArrayOfDouble
Append To List ${dbl array.double} 2.0
Append To List ${dbl array.double} 3.0
${result}= Call Soap Method GetStatistics ${dbl array}
Should Be Equal As Numbers ${result.Average} 2.5
Contents of c:/SudsLibraryExtensions.py:
from robot.libraries.BuiltIn import BuiltIn
from suds.plugin import MessagePlugin
class _MyPlugin(MessagePlugin):
def __init__(self):
self._type = 'defaultType'
def marshalled(self, context):
body = context.envelope.getChild('Body')
call = body.getChild('GetStatistics')
call.set('type', self._type)
def set_getstats_type(self, value):
self._type = value
class SudsLibraryExtensions(object):
def attach_my_plugin(self):
client = BuiltIn().get_library_instance("SudsLibrary")._client()
plugins = client.options.plugins
if any(isinstance(x, _MyPlugin) for x in plugins):
return
# prepend so SudsLibrary's plugin is left in place
plugins.insert(0, _MyPlugin())
client.set_options(plugins=plugins)
def set_getstats_type(self, value):
self._get_plugin().set_getstats_type(value)
def _get_plugin(self):
client = BuiltIn().get_library_instance("SudsLibrary")._client()
plugins = client.options.plugins
my_plugin = next((plugin for plugin in plugins if isinstance(plugin, _MyPlugin)), None)
if my_plugin is None:
raise RuntimeError("Plugin not found. Did you call Attach My Plugin?")
return my_plugin
The type attribute will always be set so long as the plugin is attached with the keyword Attach My Plugin. There is a default type. To change the value of type, the keyword Set GetStats Type is used. Any type set will be used in all future requests until it is changed. The only reason that two classes are used here is to prevent "marshalled" from becoming an exposed keyword.

Calling Cloud Stack With Parameters

i am trying to make an api call using the below code and it works fine
import urllib2
import urllib
import hashlib
import hmac
import base64
baseurl='http://www.xxxx.com:8080/client/api?'
request={}
request['command']='listUsers'
request['response']='xml'
request['apikey']='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
secretkey='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
request_str='&'.join(['='.join([k,urllib.quote_plus(request[k])]) for k in request.keys()])
sig_str='&'.join(['='.join([k.lower(),urllib.quote_plus(request[k].lower().replace('+','%20'))])for k in sorted(request.iterkeys())])
sig=hmac.new(secretkey,sig_str,hashlib.sha1)
sig=hmac.new(secretkey,sig_str,hashlib.sha1).digest()
sig=base64.encodestring(hmac.new(secretkey,sig_str,hashlib.sha1).digest())
sig=base64.encodestring(hmac.new(secretkey,sig_str,hashlib.sha1).digest()).strip()
sig=urllib.quote_plus(base64.encodestring(hmac.new(secretkey,sig_str,hashlib.sha1).digest()).strip())
req=baseurl+request_str+'&signature='+sig
res=urllib2.urlopen(req)
result = res.read()
print result
what i want to know how can i send additional parameter with the Api call??
and how to send parameters when iam sending data to cloud stack instead of getting from the cloud stack
e.g createuser
Add additional parameters to the the request dictionary.
E.g. listUsers allows details of a specific username to be listed (listUsers API Reference). To do so, you'd update request creation as follows:
request={}
request['command']='listUsers'
request['username']='admin'
request['response']='xml'
request['apikey']='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Also the Rules for Signing say to "Lower case the entire Command String and sort it alphabetically via the field for each field-value pair" This section of the docs also covers adding an expiry to the URL.
Finally, you need to ensure the HTTP GET is not cached by network infrastructure by making each HTTP GET unique. The CloudStack API uses a cache buster. Alternatively, you can add an expiry to each query, or use an HTTP POST.

Routing based on query parameter in Play framework

My web application will be triggered from an external system. It will call one request path of my app, but uses different query parameters for different kinds of requests.
One of the parameters is the "action" that defines what is to be done. The rest of the params depend on the "action".
So I can get request params like these:
action=sayHello&user=Joe
action=newUser&name=Joe&address=xxx
action=resetPassword
...
I would like to be able to encode it similarly in the routes file for play so it does the query param based routing and as much of the validation of other parameters as possible.
What I have instead is one routing for all of these possibilities with plenty of optional parameters. The action processing it starts with a big pattern match to do dispatch and parameter validation.
Googling and checking SO just popped up plenty of samples where the params are encoded in the request path somehow, so multiple paths are routed to the same action, but I would like the opposite: one path routed to different actions.
One of my colleagues said we could have one "dispatcher" action that would just redirect based on the "action" parameter. It would be a bit more structured then the current solution, but it would not eliminate the long list of optional parameters which should be selectively passed to the next action, so I hope one knows an even better solution :-)
BTW the external system that calls my app is developed by another company and I have no influence on this design, so it's not an option to change the way how my app is triggered.
The single dispatcher action is probably the way to go, and you don't need to specify all of your optional parameters in the route. If action is always there then that's the only one you really need.
GET /someRoute controller.dispatcher(action: String)
Then in your action method you can access request.queryString to get any of the other optional parameters.
Note: I am NOT experienced Scala developer, so maybe presented snippets can be optimized... What's important for you they are valid and working.
So...
You don't need to declare every optional param in the routes file. It is great shortcut for type param's validation and best choice would be convince 'other company' to use API prepared by you... Anyway if you haven't such possibility you can also handle their requests as required.
In general: the dispatcher approach seems to be right in this place, fortunately you don't need to declare all optional params in the routes and pass it between actions/methods as they can be fetched directly from request. In PHP it can be compared to $_GET['action'] and in Java version of Play 2 controller - DynamicForm class - form().bindFromRequest.get("action").
Let's say that you have a route:
GET /dispatcher controllers.Application.dispatcher
In that case your dispatcher action (and additional methods) can look like:
def dispatcher = Action { implicit request =>
request.queryString.get("action").flatMap(_.headOption).getOrElse("invalid") match {
case "sayHello" => sayHelloMethod
case "newUser" => newUserMethod
case _ => BadRequest("Action not allowed!")
}
}
// http://localhost:9000/dispatcher?action=sayHello&name=John
def sayHelloMethod(implicit request: RequestHeader) = {
val name = request.queryString.get("name").flatMap(_.headOption).getOrElse("")
Ok("Hello " + name )
}
// http://localhost:9000/dispatcher?action=newUser&name=John+Doe&address=john#doe.com
def newUserMethod(implicit request: RequestHeader) = {
val name = request.queryString.get("name").flatMap(_.headOption).getOrElse("")
val address = request.queryString.get("address").flatMap(_.headOption).getOrElse("")
Ok("We are creating new user " + name + " with address " + address)
}
Of course you will need to validate incoming types and values 'manually', especially when actions will be operating on the DataBase, anyway biggest part of your problem you have resolved now.