Enabling CORS in Python with Tastypie update (PUT) - python

There are several questions on SO related to this topic, but none that I have seen solve my issue.
I have an endpoint in my Django/Tastypie API that accepts a PUT in order to update the database. This works great when testing in localhost:8000, however, my production database is located in a different domain, so I need to enable CORS to get this PUT call to update the database.
I have found the tutorial here that gives an example of how to do this, however, when I execute the cURL command:
curl -X PUT --dump-header - -H "Content-Type: application/json" -H "Authorization: ApiKey api:MYAPIKEY" -d "{\"mykey\": \"my_value\", \"resource_uri\": \"/api/v1/mytable/362/\"}" my.domain.com/api/v1/mytable/362/
I am still receiving 401 UNAUTHORIZED for my calls (header dump below):
HTTP/1.1 401 UNAUTHORIZED
Date: Mon, 22 Sep 2014 16:08:34 GMT
Server: Apache/2.4.7 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
X-Frame-Options: SAMEORIGIN
Content-Length: 0
Content-Type: text/html; charset=utf-8
My CoresResource superclass code:
class CorsResource(ModelResource):
""" adds CORS headers for cross-domain requests """
def patch_response(self, response):
allowed_headers = ['Content-Type', 'Authorization']
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Headers'] = ','.join(allowed_headers)
return response
def dispatch(self, *args, **kwargs):
""" calls super and patches resonse headers
or
catches ImmediateHttpResponse, patches headers and re-raises
"""
try:
response = super(CorsResource, self).dispatch(*args, **kwargs)
return self.patch_response(response)
except ImmediateHttpResponse, e:
response = self.patch_response(e.response)
# re-raise - we could return a response but then anthing wrapping
# this and expecting an exception would be confused
raise ImmediateHttpResponse(response)
def method_check(self, request, allowed=None):
""" Handle OPTIONS requests """
if request.method.upper() == 'OPTIONS':
if allowed is None:
allowed = []
allows = ','.join([s.upper() for s in allowed])
response = HttpResponse(allows)
response['Allow'] = allows
raise ImmediateHttpResponse(response=response)
return super(CorsResource, self).method_check(request, allowed)
My endpoint code:
class DataModelResource(CorsResource):
data = fields.OneToOneField(DataModelResource, "data", full=True)
class Meta:
allowed_methods = ['get', 'put']
authentication = ApiKeyAuthentication()
authorization = Authorization()
queryset = Data.objects.all()
resource_name = 'mytable'
Does anyone see any reason why making a PUT from a cross-domain should be failing with this code???
I'm at a complete loss here.

Related

Response content shorter than Content-Length

I'm doing a FastAPI app, with a function that authenticates to a CouchDB instance. In order to request Couchdb, I use the (yet unmaintained) library python-couchdb.
Here is the relevant portion of code that illustrates my issue:
from fastapi import FastAPI
from couchdb import http
resource = http.Resource(url, http.Session())
from couchdb import Unauthorized
from pydantic import BaseModel
app = FastAPI()
class RegisteredUser(BaseModel):
email: str
password: str
#app.post("/login")
async def log_user(user: RegisteredUser):
# some email format verifications here
# ...
try:
status, headers, _ = resource.post_json('_session', {
'name': user.email,
'password': user.password,
})
except Exception as e:
if isinstance(e, Unauthorized):
return 403
else:
return 500
# tests
print(headers)
The headers look like:
Cache-Control: must-revalidate
Content-Length: 54
Content-Type: application/json
Date: Sat, 08 Aug 2020 19:19:49 GMT
Server: CouchDB/3.1.0 (Erlang OTP/22)
Set-Cookie: AuthSession=am9zZWJvdmVAam9zZWJvdmUuY29tOjVGMkVGQUQ2Op-UUD22VvdxYzbMNp92e30Er_z0; Version=1; Expires=Sat, 08-Aug-2020 20:59:50 GMT; Max-Age=6000; Path=/; HttpOnly
At this point (if no error raised), I'd like to send back to the (browser) client the cookie that CouchDb provides. Something like:
...
# tests
print(headers)
if status == 200 and 'Set-Cookie' in headers:
return JSONResponse(content=True, headers=headers)
else:
return status
I'm not used to sessions-cookie and I'm not sure if I should send back the full headers or just the headers['Set-Cookie'] part
Whatever I do, I end up with the same error message
RuntimeError: Response content shorter than Content-Length
Would you mine to explain to me what the error is saying, and how I can solve my case? Ty!
Found this SO thread but no clue neither FastAPI middleware peeking into responses
Here is one solution:
if status == 200 and 'Set-Cookie' in headers:
return JSONResponse(content=True, headers={
'Set-Cookie' : headers['Set-Cookie']
})
else:
return status
Ty #HernánAlarcón

PUT request is returning an error with flask

I use Flask in an API application and I have this function:
#app.route('/v1/myapi/editperson/<int:id_person>)', methods=['PUT'])
def edit_person(person_id):
req_data = request.get_json()
firstname = req_data.get('firstname')
lastname = req_data.get('lastname')
session.query(model.Persons).filter(model.Persons.id==person_id).update( firstname, lastname)
session.commit()
session.close()
return jsonify(req_data)
When I execute this curl request:
curl -i -H "Content-Type: application/json" -X PUT -d '{"firstname": "myfirstname", "lastname": "mylastname"}' http://localhost:5000/v1/myapi/editperson/38
I get this error:
HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 27
Server: Werkzeug/0.14.1 Python/2.7.6
Date: Wed, 04 Jul 2018 07:17:09 GMT
Proxy-Connection: keep-alive
Connection: keep-alive
{
"error": "Not found"
}
I do have a row in the database with the id = 38 so I don't know why the request doesn't find it.
I use SQLAlchemy to query the database.
Any clues?
Indeed PUT/POST but also:
#app.route('/v1/myapi/editperson/<int:id_person>)', methods=['PUT'])
vs.
#app.route('/v1/myapi/editperson/<int:id_person>', methods=['PUT'])
Your annotation specifies that this API just allow PUT method. But you request this API through POST method. Try:
curl -X PUT ....
Your route passes id_person but your query uses person_id

Create a HTTP object from a string in Python

I am having a device which is sending the following http message to my RaspberryPi:
POST /sinvertwebmonitor/InverterService/InverterService.asmx/CollectInverterData HTTP/1.1
Host: www.automation.siemens.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 349
xmlData=<rd><m>xxxxx</m><s>yyyyyy</s><d t="1483019400" l="600"><p i="1">460380AE</p><p i="2">43655DE7</p><p i="3">4212C986</p><p i="4">424805BC</p><p i="5">4604E3D1</p><p i="6">441F616A</p><p i="7">4155E7F5</p><p i="8">E1</p><p i="9">112</p><p i="C">153</p><p i="D">4</p><p i="E">11ABAC</p><p i="F">22A48C</p><p i="10">0</p></d></rd>
I cannot change anything on the device.
On the RaspberryPi im running a script to listen and receive the message from a socket.
This works so far and the received message is the one above.
Now, I would like to create a HTTP object from this message and then extract comfortably the header, content and so on.
Similar to the following example:
r = requests.get('https://www.google.com')
r.status_code
However, without "getting" an url. I just want to read the string I already have.
Pseudo-example:
r = requests.read(hereComesTheString)
r.status_code
I hope the problem became understandable.
Would be glad to get some hints.
Thanks and best regards,
Christoph
You use the status_code property in your example, but what you are receiving is a request not a response. However you can still create a simple object for accessing the data in the request.
It is probably easiest to create your own custom class:
import mimetools
from StringIO import StringIO
request = """POST /sinvertwebmonitor/InverterService/InverterService.asmx/CollectInverterData HTTP/1.1
Host: www.automation.siemens.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 349
xmlData=<rd><m>xxxxx</m><s>yyyyyy</s><d t="1483019400" l="600"><p i="1">460380AE</p><p i="2">43655DE7</p><p i="3">4212C986</p><p i="4">424805BC</p><p i="5">4604E3D1</p><p i="6">441F616A</p><p i="7">4155E7F5</p><p i="8">E1</p><p i="9">112</p><p i="C">153</p><p i="D">4</p><p i="E">11ABAC</p><p i="F">22A48C</p><p i="10">0</p></d></rd>"""
class Request:
def __init__(self, request):
stream = StringIO(request)
request = stream.readline()
words = request.split()
[self.command, self.path, self.version] = words
self.headers = mimetools.Message(stream, 0)
self.content = stream.read()
def __getitem__(self, key):
return self.headers.get(key, '')
r = Request(request)
print(r.command)
print(r.path)
print(r.version)
for header in r.headers:
print(header, r[header])
print(r.content)
This outputs:
POST
/sinvertwebmonitor/InverterService/InverterService.asmx/CollectInverterData
HTTP/1.1
('host', 'www.automation.siemens.com')
('content-type', 'application/x-www-form-urlencoded')
('content-length', '349')
xmlData=<rd><m>xxxxx</m><s>yyyyyy</s><d t="1483019400" l="600"><p i="1">460380AE</p><p i="2">43655DE7</p><p i="3">4212C986</p><p i="4">424805BC</p><p i="5">4604E3D1</p><p i="6">441F616A</p><p i="7">4155E7F5</p><p i="8">E1</p><p i="9">112</p><p i="C">153</p><p i="D">4</p><p i="E">11ABAC</p><p i="F">22A48C</p><p i="10">0</p></d></rd>
If you're using plain socket server, then you need to implement an HTTP server so that you can split the request and respond according to the protocol.
It's probably easier just to use an existing HTTP server and app server. Flask is ideal for this:
from flask import Flask
from flask import request
app = Flask(__name__)
#app.route("/sinvertwebmonitor/InverterService/InverterService.asmx/CollectInverterData", methods=['POST'])
def dataCollector():
data = request.form['xmlData']
print(data)
# parseData. Take a look at ElementTree
if __name__ == "__main__":
app.run(host=0.0.0.0, port=80)
Thanks Alden. Below your code with a few changes so it works with Python3.
import email
from io import StringIO
request = """POST /sinvertwebmonitor/InverterService/InverterService.asmx/CollectInverterData HTTP/1.1
Host: www.automation.siemens.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 349
xmlData=<rd><m>xxxxx</m><s>yyyyyy</s><d t="1483019400" l="600"><p i="1">460380AE</p><p i="2">43655DE7</p><p i="3">4212C986</p><p i="4">424805BC</p><p i="5">4604E3D1</p><p i="6">441F616A</p><p i="7">4155E7F5</p><p i="8">E1</p><p i="9">112</p><p i="C">153</p><p i="D">4</p><p i="E">11ABAC</p><p i="F">22A48C</p><p i="10">0</p></d></rd>"""
class Request:
def __init__(self, request):
stream = StringIO(request)
request = stream.readline()
words = request.split()
[self.command, self.path, self.version] = words
self.headers = email.message_from_string(request)
self.content = stream.read()
def __getitem__(self, key):
return self.headers.get(key, '')
r = Request(request)
print(r.command)
print(r.path)
print(r.version)
for header in r.headers:
print(header, r[header])
print(r.content)

Flask test doesn't populate request.authorization when username and password are URL encoded

I'm trying to use the following test:
def post_webhook(self, payload, **kwargs):
webhook_username = 'test'
webhook_password = 'test'
webhook_url = 'http://{}:{}#localhost:8000/webhook_receive'
webhook_receive = self.app.post(
webhook_url.format(webhook_username, webhook_password),
referrer='http://localhost:8000',
json=payload)
return webhook_receive.status_code
However the main issue is request.authorization is None. Though if I launch the server and use curl -X POST <webhook_url> or requests.post(<webhook_url>), then request.authorization is properly populated.
Trying to figure out the main issue of how to fix this problem.
Using the snippet code and the Flask Test Client, the next pytest code works for me. The way I send the HTTP Basic Auth is the same way curl and HTTPie send it; in the Authorization header with the user and password encoded in base64.
import base64
from app import app
def test_secret_endpoint():
client = app.test_client()
# testing a route without authentication required
rv = client.get('/')
assert rv.status_code == 200
# testing a secured route without credentials
rv = client.post('/secret-page')
assert rv.status_code == 401
# testing a secured route with valid credentials
value = base64.encodestring('admin:secret').replace('\n', '')
headers = {'Authorization': 'Basic {}'.format(value)}
rv = client.post('/secret-page', headers=headers)
assert rv.status_code == 200
assert 'This is secret' in rv.data
The route definitions are:
#app.route('/')
def index():
return 'Hello World :)'
#app.route('/secret-page', methods=['POST'])
#requires_auth
def secret_page():
return 'This is secret'
The request header sending the credentials looks something like this:
POST /secret-page HTTP/1.1
Accept: */*
Authorization: Basic YWRtaW46c2VjcmV0
Connection: keep-alive
Content-Length: 0
Host: localhost:5000
...

webapp2 How to remove Cache-Control: no-cache from response header?

I'm having trouble serving an appengine blobstore over HTTPS, specifically with IE 8 & IE 7 browsers, as the browser just does not like to serve downloadable contents over https.
According to a microsoft article, it's because of the Cache-Control: no-cache header.
http://blogs.msdn.com/b/ieinternals/archive/2009/10/02/internet-explorer-cannot-download-over-https-when-no-cache.aspx
The solution proposed in the article is to remove the Cache-Control: no-cache header from the response.
However, it seems that webapp2 automatically adds this header even after I tried setting it to something else.
According to the source code, it seems to be added to every response
http://code.google.com/p/webapp-improved/source/browse/webapp2.py#362
def __init__(self, *args, **kwargs):
"""Constructs a response with the default settings."""
super(Response, self).__init__(*args, **kwargs)
self.headers['Cache-Control'] = 'no-cache'
Is there a way to override this behavior?
This is what I tried to do when building my response, but still the 'Cache-Control: no-cache' would still be there once the response is rendered.
self.response.headers['Pragma'] = 'cache-control'
self.response.headers['Cache-Control'] = 'private'
self.response.cache_control.no_cache = None
self.response.cache_control.public = False
self.response.cache_control.max_age = 1
I do not use webapp2 in the download handler itself. It looks like this:
class DynServe(blobstore_handlers.BlobstoreDownloadHandler):
def get(self, resource):
(key, _, _) = resource.rpartition('.')
blob_info = blobstore.BlobInfo.get(key)
self.send_blob(blob_info, save_as=True)
app = webapp2.WSGIApplication(
[
('/dynserve/([^/]+)?', DynServe),
], debug=True)
def main():
app.run()
To serve a pdf like: /dynserve/{{ blob-key }}.pdf

Categories

Resources