Handle Facebook Deauthorize Callback in Python - python

I am building a Django Application that makes use of the Facebook Login using django-allauth. I would like to know when an user removes my corresponding Facebook App, and Facebook provides such functionality in the form of a deauthorize callback. There are also some instructions on how to parse the request using PHP in the documentation.
However, translating this into Python doesn't seem to be as easy as I thought, as I am getting 'Padding Errors' when decoding the posted base64-encoded string, which seems very odd to me.

The problem seems to be that a certain padding has to be manually added to the posted data. Here is a working example:
class DeauthorizeView(View):
def post(self, request, *args, **kwargs):
try:
signed_request = request.POST['signed_request']
encoded_sig, payload = signed_request.split('.')
except (ValueError, KeyError):
return HttpResponse(status=400, content='Invalid request')
try:
# Reference for request decoding: https://developers.facebook.com/docs/games/gamesonfacebook/login#parsingsr
# For some reason, the request needs to be padded in order to be decoded. See https://stackoverflow.com/a/6102526/2628463
decoded_payload = base64.urlsafe_b64decode(payload + "==").decode('utf-8')
decoded_payload = json.loads(decoded_payload)
if type(decoded_payload) is not dict or 'user_id' not in decoded_payload.keys():
return HttpResponse(status=400, content='Invalid payload data')
except (ValueError, json.JSONDecodeError):
return HttpResponse(status=400, content='Could not decode payload')
try:
secret = SocialApp.objects.get(id=1).secret
sig = base64.urlsafe_b64decode(encoded_sig + "==")
expected_sig = hmac.new(bytes(secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256)
except:
return HttpResponse(status=400, content='Could not decode signature')
if not hmac.compare_digest(expected_sig.digest(), sig):
return HttpResponse(status=400, content='Invalid request')
user_id = decoded_payload['user_id']
try:
social_account = SocialAccount.objects.get(uid=user_id)
except SocialAccount.DoesNotExist:
return HttpResponse(status=200)
# Own custom logic here
return HttpResponse(status=200)

Related

How to call a normal python function along with header information without using requests

Details of application:
UI: Angular
Backend: Python Flask (using Swagger)
Database: MongoDB
We have a few backend python methods which will be called from the UI side to do CURD operations on the database.
Each of the methods has a decorator which will check the header information to ensure that only a genuine person can call the methods.
From the UI side when these API's are called, this authorization decorator is not creating any problem and a proper response is returned to the UI (as we are passing the header information also to the request)
But now we are writing unit test cases for the API's. Here each test case will call the backend method and because of the authorization decorator, I am getting errors and not able to proceed. How can I handle this issue?
backend_api.py
--------------
from commonlib.auth import require_auth
#require_auth
def get_records(record_id):
try:
record_details = records_coll.find_one({"_id": ObjectId(str(record_id))})
if record_details is not None:
resp = jsonify({"msg": "Found Record", "data": str(record_details)})
resp.status_code = 200
return resp
else:
resp = jsonify({"msg": "Record not found"})
resp.status_code = 404
return resp
except Exception as ex:
resp = jsonify({"msg": "Exception Occured",'Exception Details': ex}))
resp.status_code = 500
return resp
commonlib/auth.py
-----------------
### some lines of code here
def require_auth(func):
"""
Decorator that can be added to a function to check for authorization
"""
def wrapper(*args, **kwargs):
print(*args,**kwargs)
username = get_username()
security_log = {
'loginId': username,
'securityProtocol': _get_auth_type(),
}
try:
if username is None:
raise SecurityException('Authorization header or cookie not found')
if not is_auth_valid():
raise SecurityException('Authorization header or cookie is invalid')
except SecurityException as ex:
log_security(result='DENIED', message=str(ex))
unauthorized(str(ex))
return func(*args, **kwargs)
return wrapper
test_backend_api.py
-------------------
class TestBackendApi(unittest.TestCase):
### some lines of code here
#mock.patch("pymongo.collection.Collection.find_one", side_effect=[projects_json])
def test_get_records(self, mock_call):
from backend_api import get_records
ret_resp = get_records('61729c18afe7a83268c6c9b8')
final_response = ret_resp.get_json()
message1 = "return response status code is not 200"
self.assertEqual(ret_resp.status_code, 200, message1)
Error snippet :
---------------
E RuntimeError: Working outside of request context.
E
E This typically means that you attempted to use functionality that needed
E an active HTTP request. Consult the documentation on testing for
E information about how to avoid this problem.

how to improve exception handling in python/django

This is an example of my exception handling in a django project:
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except requests.exceptions.ConnectionError as e:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
return render(request, 'box/error.html', context)
except requests.exceptions.RequestException as e:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
return render(request, 'box/error.html', context)
There is only two excepts now, but it should be more for the several request exceptions. But already the view method is bloated up by this. Is there a way to forward the except handling to a separate error method?
There is also the problem, that I need here to call the render message for each except, I would like to avoid that.
And here I also repeat for each except "could not connect to your box because", that should be set once when there appeared any exception.
I can solve it by something like this:
try:
box.connect()
except Exception as e:
return error_handling(request, e)
-
def error_handling(request, e):
if type(e).__name__ == requests.exceptions.ConnectionError.__name__:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
elif type(e).__name__ == requests.exceptions.RequestException.__name__:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
else:
context = {'error_message': 'There was an unkown error, sorry.'}
return render(request, 'box/error.html', context)
and I could of course improve the error message thing then. But overall, is it a pythonic way to handle exceptions with if/else? For example I could not catch RequestException here if ConnectionError is thrown, so I would need to catch each requests error, that looks more like an ugly fiddling...
This is a use case for decorators. If it's something more general that applies to all views (say, error logging), you can use the Django exception middleware hook, but that doesn't seem to be the case here.
With respect to the repetitive error string problem, the Pythonic way to solve it is to have a constant base string with {replaceable_parts} inserted, so that later on you can .format() them.
With this, say we have the following file decorators.py:
import functools
from django.shortcuts import render
from requests.exceptions import ConnectionError, RequestException
BASE_ERROR_MESSAGE = 'Could not connect to your box because {error_reason}'
def handle_view_exception(func):
"""Decorator for handling exceptions."""
#functools.wraps(func)
def wrapper(request, *args, **kwargs):
try:
response = func(request, *args, **kwargs)
except RequestException as e:
error_reason = 'of an unknown error.'
if isinstance(e, ConnectionError):
error_reason = 'the host is unknown.'
context = {
'error_message': BASE_ERROR_MESSAGE.format(error_reason=error_reason),
}
response = render(request, 'box/error.html', context)
return response
return wrapper
We're using the fact that ConnectionError is a subclass of RequestException in the requests library. We could also do a dictionary with the exception classes as keys, but the issue here is that this won't handle exception class inheritance, which is the kind of omission that generates subtle bugs later on. The isinstance function is a more reliable way of doing this check.
If your exception tree keeps growing, you can keep adding if statements. In case that starts to get unwieldy, I recommend looking here, but I'd say it's a code smell to have that much branching in error handling.
Then in your views:
from .decorators import handle_view_exception
#handle_view_exception
def boxinfo(request, url: str):
box = get_box(url)
box.connect()
...
That way the error handling logic is completely separate from your views, and best of all, it's reusable.
could you have something like this:
views.py
EXCEPTION_MAP = {
ConnectionError: "Could not connect to your box because the host is unknown.",
RequestException: "Could not connect to your box because of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
message = EXCEPTION_MAP.get(type(e)) or UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
You could then just expand the EXCEPTION_MAP and the except () for any other known exception types you're expecting to catch?
if you want to reduce the duplication of "Could not connect to your box because ...
You could maybe do:
views.py
BASE_ERROR_STRING = "Could not connect to your box because {specific}"
EXCEPTION_MAP = {
ConnectionError: "the host is unknown.",
RequestException: "of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
specific_message = EXCEPTION_MAP.get(type(e))
if specific_message:
message = BASE_ERROR_STRING.format(specific=specific_message)
else:
message = UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)

Pythonic way of checking parameter is an integer and setting a default value if no parameter

I want to get a GET parameter in my Flask app. I would like to check that this parameter is an integer if it was submitted.
If it was not submitted I would like to set a default value.
If it was submitted but is invalid, I would like to throw an error
I came up with the following code which works but seems ugly and unpythonic to me. What is the most pythonic way of doing this?
#app.route('/')
def index():
page = request.args.get('page') or 0
try:
page = int(page)
except:
return abort(500)
You can tell the request.args.get() method to test for your type, and set a default:
#app.route('/')
def index():
page = request.args.get('page', type=int, default=0)
If type conversion fails, the default is used. In a web environment that's a much, much better idea than to raise a 500 response (which is reserved for server errors, while not providing a valid page number is really a client error).
request.args.get() will otherwise never raise an exception; it returns None instead if no other default was set. If you must have an exception on type conversion, you are stuck with your approach, but I'd return a 400 Bad Request error code instead:
#app.route('/')
def index():
page_str = request.args.get('page', default=0)
try:
page = int(page_str)
except ValueError:
abort(400)
abort(...) raises an exception, no need to use return there. Because an exception is raised, you could make that whole block of 4 lines a separate function:
def convert_or_400(value, type):
"""Convert a value to the given type, or raise a 400 client error"""
try:
return type(value)
except ValueError:
abort(400)
Now the route becomes:
#app.route('/')
def index():
page = convert_or_400(request.args.get('page', default=0), int)
def index():
page = input("enter the value")
try:
if (page != ""):
page = int(page)
else:
page = 0
print("Input value..",page)
except:
print("wrong value given.Input numeric value")
index()
A simple way

Content-Type header not getting set in Tornado

I have the following base class:
class CorsHandler(tornado.web.RequestHandler):
def set_default_headers(self):
super(CorsHandler, self).set_default_headers()
self.set_header('Access-Control-Allow-Origin', self.request.headers.get('Origin', '*'))
self.set_header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS')
self.set_header('Access-Control-Allow-Credentials', 'true')
self.set_header('Access-Control-Allow-Headers', ','.join(
self.request.headers.get('Access-Control-Request-Headers', '').split(',') +
['Content-Type']
))
self.set_header('Content-Type', 'application/json')
def options(self, *args, **kwargs):
pass
And the following handler:
def get(self, resource_id=None, field=None):
try:
if resource_id is None:
response = self.resource.query.filter_by(is_deleted=False).all()
else:
record = self.resource.query.get(int(resource_id))
if field is None:
response = record
else:
response = {field: getattr(record, field)}
self.db.session.commit()
except Exception, e:
self.db.session.rollback()
self.send_error(500, message=e.message)
self.write(response)
Everything's pretty straightforward, except Content-Type is not getting set. Note that any other header is being set properly.
What's going on?
It seems this is a 304 Not Modified response. Remember only the first 200 OK response contains Content-Type header. The following response will neglect this header if you are requesting the same resource.
And beware that you don't actually need to explicitly set the Content-Type. If you look into the source code of Tornado, you will find this in the comment of write(self, chunk):
If the given chunk is a dictionary, we write it as JSON and set the Content-Type of the response to be application/json. (if you want to send JSON as a different Content-Type, call set_header after calling write()).

Python nose test failing on JSON response

This is the method in ReportRunner class in report_runner.py in my Flask-Restful app:
class ReportRunner(object):
def __init__(self):
pass
def setup_routes(self, app):
app.add_url_rule("/run_report", view_func=self.run_report)
def request_report(self, key):
# code #
def key_exists(self, key):
# code #
def run_report(self):
key = request.args.get("key", "")
if self.key_exists(key):
self.request_report(report_type, key)
return jsonify(message = "Success! Your report has been created.")
else:
response = jsonify({"message": "Error => report key not found on server."})
response.status_code = 404
return response
and the nose test calls the URL associated with that route
def setUp(self):
self.setup_flask()
self.controller = Controller()
self.report_runner = ReportRunner()
self.setup_route(self.report_runner)
def test_run_report(self):
rr = Report(key = "daily_report")
rr.save()
self.controller.override(self.report_runner, "request_report")
self.controller.expectAndReturn(self.report_runner.request_report("daily_report"), True )
self.controller.replay()
response = self.client.get("/run_report?key=daily_report")
assert_equals({"message": "Success! Your report has been created."}, response.json)
assert_equals(200, response.status_code)
and the test was failing with the following message:
AttributeError: 'Response' object has no attribute 'json'
but according to the docs it seems that this is how you do it. Do I change the return value from the method, or do I need to structure the test differently?
The test is now passing written like this:
json_response = json.loads(response.data)
assert_equals("Success! Your report has been created.", json_response["message"])
but I'm not clear on the difference between the two approaches.
According to Flask API Response object doesn't have attribute json (it's Request object that has it). So, that's why you get exception. Instead, it has generic method get_data() that returns the string representation of response body.
json_response = json.loads(response.get_data())
assert_equals("Success! Your report has been created.", json_response.get("message", "<no message>"))
So, it's close to what you have except:
get_data() is suggested instead of data as API says: This should not be used and will eventually get deprecated.
reading value from dictionary with get() to not generate exception if key is missing but get correct assert about missing message.
Check this Q&A also.

Categories

Resources