Is the signed request required for a Facebook Canvas app? - python

I saw that OAuth is recommended also for facebook canvas apps and the code I had didn't work, it tried to use the signed_request but every time i changed the session I had to do a reload:
class Facebook(object):
"""Wraps the Facebook specific logic"""
def __init__(self, app_id=conf.FACEBOOK_APP_ID,
app_secret=conf.FACEBOOK_APP_SECRET):
self.app_id = app_id
self.app_secret = app_secret
self.user_id = None
self.access_token = None
self.signed_request = {}
def api(self, path, params=None, method=u'GET', domain=u'graph'):
"""Make API calls"""
if not params:
params = {}
params[u'method'] = method
if u'access_token' not in params and self.access_token:
params[u'access_token'] = self.access_token
result = json.loads(urlfetch.fetch(
url=u'https://' + domain + u'.facebook.com' + path,
payload=urllib.urlencode(params),
method=urlfetch.POST,
headers={
u'Content-Type': u'application/x-www-form-urlencoded'})
.content)
if isinstance(result, dict) and u'error' in result:
raise FacebookApiError(result)
return result
def load_signed_request(self, signed_request):
"""Load the user state from a signed_request value"""
try:
sig, payload = signed_request.split(u'.', 1)
sig = self.base64_url_decode(sig)
data = json.loads(self.base64_url_decode(payload))
expected_sig = hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest()
# allow the signed_request to function for upto 1 day
if sig == expected_sig and \
data[u'issued_at'] > (time.time() - 86400):
self.signed_request = data
self.user_id = data.get(u'user_id')
self.access_token = data.get(u'oauth_token')
except ValueError, ex:
pass # ignore if can't split on dot
#property
def user_cookie(self):
"""Generate a signed_request value based on current state"""
if not self.user_id:
return
payload = self.base64_url_encode(json.dumps({
u'user_id': self.user_id,
u'issued_at': str(int(time.time())),
}))
sig = self.base64_url_encode(hmac.new(
self.app_secret, msg=payload, digestmod=hashlib.sha256).digest())
return sig + '.' + payload
#staticmethod
def base64_url_decode(data):
data = data.encode(u'ascii')
data += '=' * (4 - (len(data) % 4))
return base64.urlsafe_b64decode(data)
#staticmethod
def base64_url_encode(data):
return base64.urlsafe_b64encode(data).rstrip('=')
class CsrfException(Exception):
pass
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
csrf_protect = True
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
self.init_csrf()
self.response.headers[u'P3P'] = u'CP=HONK' # iframe cookies in IE
except Exception, ex:
self.log_exception(ex)
raise
def handle_exception(self, ex, debug_mode):
"""Invoked for unhandled exceptions by webapp"""
self.log_exception(ex)
self.render(u'error',
trace=traceback.format_exc(), debug_mode=debug_mode)
def log_exception(self, ex):
"""Internal logging handler to reduce some App Engine noise in errors"""
msg = ((str(ex) or ex.__class__.__name__) +
u': \n' + traceback.format_exc())
if isinstance(ex, urlfetch.DownloadError) or \
isinstance(ex, DeadlineExceededError) or \
isinstance(ex, CsrfException) or \
isinstance(ex, taskqueue.TransientError):
logging.warn(msg)
else:
logging.error(msg)
def set_cookie(self, name, value, expires=None):
"""Set a cookie"""
if value is None:
value = 'deleted'
expires = datetime.timedelta(minutes=-50000)
jar = Cookie.SimpleCookie()
jar[name] = value
jar[name]['path'] = u'/'
if expires:
if isinstance(expires, datetime.timedelta):
expires = datetime.datetime.now() + expires
if isinstance(expires, datetime.datetime):
expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
jar[name]['expires'] = expires
self.response.headers.add_header(*jar.output().split(u': ', 1))
def render(self, name, **data):
"""Render a template"""
if not data:
data = {}
data[u'js_conf'] = json.dumps({
u'appId': conf.FACEBOOK_APP_ID,
u'canvasName': conf.FACEBOOK_CANVAS_NAME,
u'userIdOnServer': self.user.user_id if self.user else None,
})
data[u'logged_in_user'] = self.user
data[u'message'] = self.get_message()
data[u'csrf_token'] = self.csrf_token
data[u'canvas_name'] = conf.FACEBOOK_CANVAS_NAME
self.response.out.write(template.render(
os.path.join(
os.path.dirname(__file__), 'templates', name + '.html'),
data))
def init_facebook(self):
"""Sets up the request specific Facebook and User instance"""
facebook = Facebook()
user = None
# initial facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# we reset the method to GET because a request from facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. in webapp causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# refresh data if we failed in doing so after a realtime ping
if user.dirty:
user.refresh_data()
# restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # ignore if can't get the minimum fields
self.facebook = facebook
self.user = user
def init_csrf(self):
"""Issue and handle CSRF token as necessary"""
self.csrf_token = self.request.cookies.get(u'c')
if not self.csrf_token:
self.csrf_token = str(uuid4())[:8]
self.set_cookie('c', self.csrf_token)
if self.request.method == u'POST' and self.csrf_protect and \
self.csrf_token != self.request.POST.get(u'_csrf_token'):
raise CsrfException(u'Missing or invalid CSRF token.')
def set_message(self, **obj):
"""Simple message support"""
self.set_cookie('m', base64.b64encode(json.dumps(obj)) if obj else None)
def get_message(self):
"""Get and clear the current message"""
message = self.request.cookies.get(u'm')
if message:
self.set_message() # clear the current cookie
return json.loads(base64.b64decode(message))
I changed the code above to OAuth serverside for the canvas app and then I could make the app behave like I wanted to. But if I use OAuth 2.0 do I really need the signed_request? If I use OAuth the signed_request seems unneccessary and OAuth can do it all. I changed the function init_facebookrather much:
def init_facebook(self):
facebook = Facebook()
user = None
# initial facebook request comes in as a POST with a signed_request
if 'signed_request' in self.request.POST:
fbdata= parse_signed_request(self.request.get('signed_request'), facebookconf.FACEBOOK_APP_SECRET)
facebook.signed_request = fbdata
facebook.user_id = fbdata.get('user_id')
facebook.access_token = fbdata.get('oauth_token')
if facebook.user_id:
graph = GraphAPI(facebook.access_token)
user = graph.get_object("me") #write the access_token to the datastore
fbuser = FBUser.get_by_key_name(user["id"])
#logging.debug("fbuser "+fbuser.name)
self.user = fbuser
if not fbuser:
fbuser = FBUser(key_name=str(user["id"]),
id=str(user["id"]),
name=user["name"],
profile_url=user["link"],
access_token=facebook.access_token)
fbuser.put()
elif fbuser.access_token != facebook.access_token:
fbuser.access_token = facebook.access_token
fbuser.put()
# try to load or create a user object
if facebook.user_id:
logging.debug("loading facebook.user_id")
user = FBUser.get_by_key_name(facebook.user_id)
if user:
# update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# refresh data if we failed in doing so after a realtime ping
if user.dirty:
user.refresh_data()
# restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api('/me', {'fields': _USER_FIELDS})
try:
friends = [user['id'] for user in me['friends']['data']]
user = FBUser(key_name=facebook.user_id,
id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me['name'],
email=me.get('email'), picture=me['picture'])
user.put()
except KeyError, ex:
pass # ignore if can't get the minimum fields
self.facebook = facebook
self.user = user
Now it uses OAuth instead of changing the method from POST to GET which I never understodd why it had to do anyway. I have more code but maybe you know enough already to tell me if I'm doing this wrong and should go back to a more basic example.
For example I had some problems logging out the user and I had to write a custom logout handler:
class FBLogoutHandler(webapp2.RequestHandler):
def get(self):
logging.debug('in fblogout')
current_user = main.get_user_from_cookie(self.request.cookies, facebookconf.FACEBOOK_APP_ID, facebookconf.FACEBOOK_APP_SECRET)
if current_user:
graph = main.GraphAPI(current_user["access_token"])
profile = graph.get_object("me")
accessed_token = current_user["access_token"]
logging.debug('setting cookie')
self.set_cookie("fbsr_" + facebookconf.FACEBOOK_APP_ID, None, expires=time.time() - 86400)
logging.debug('redirecting with token '+str(accessed_token))
self.redirect('https://www.facebook.com/logout.php?next=http://www.facebook.com&access_token=%s' % accessed_token)
def set_cookie(self, name, value, expires=None):
if value is None:
value = 'deleted'
expires = datetime.timedelta(minutes=-50000)
jar = Cookie.SimpleCookie()
jar[name] = value
jar[name]['path'] = '/'
if expires:
if isinstance(expires, datetime.timedelta):
expires = datetime.datetime.now() + expires
if isinstance(expires, datetime.datetime):
expires = expires.strftime('%a, %d %b %Y %H:%M:%S')
jar[name]['expires'] = expires
self.response.headers.add_header(*jar.output().split(': ', 1))
def get_host(self):
return os.environ.get('HTTP_HOST', os.environ['SERVER_NAME'])
This solution might not be best so I wonder what alternatives there are for my canvas app?
Thanks

The signed_request is always updated when you access a canvas app (when refreshing the parent frame), so it's a good way of getting the latest access_token for the user (which usually only lasts for a hour).
The access_token obtained using oauth has the same expiration time, so if you rely solely on oauth, you will have to authenticate the user ever hour.
I tend to use a combination of the two. On initial use of the app, I use oauth to get the access_token and access the API. Then, on subsequent visits, I rely on the signed_request to get the access_token and automatically personalise the content for the user (since it tell me who they are and gives me access to the API without following the OAuth flow again).

Related

Having issues with sending JWT via AXIOS ..normal routes work however AJAX post says no JWT available

So I have this endpoint below(Block 2) called from the directly below(Block 1)...the headers I see in the browser appear to be sending the access_token_cookie in the cookie header....I also have a after_header block(Block 3)
THE AXIOS CALL FROM THE PAGE
var myFunction = function(element){
console.log(element.innerText);
axios.defaults.withCredentials = true;
axios.post('/api/cmsaudit/'+element.innerText).then(function(response){
//console.log(response.data);
document.getElementById('resultcol').innerHTML = response.data;
}).catch(function(error){
})
THE AJAX ENDPOINT CALL
#cms_blueprint.route('/api/cmsaudit/<cause>',methods=['POST','GET'])
#jwt_required() <---- if I remove this it works fine.
def applist(cause):
f = open('cause.txt','w')
f.write('inhere')
f.write(str(datetime.now()))
getapps = OracleConnect()
opencon = getapps.openconnection()
if opencon == 0:
if cause == 'List Applications':
getapps.mycursor.execute("QUERY")
result = getapps.mycursor.fetchall()
getapps.closeconnection
print(result)
results = {}
results["value"]=[]
for x in result:
results["value"].append(x[0])
sys.stdout.flush()
return render_template('cmsaudit/applist.html',applist=results["value"])
return jsonify(results),200
else:
f.write('else is running')
return jsonify({'msg':str(opencon)})
AFTER REQUEST BLOCK TO CHECK/REFRESH TOKEN
#app.after_request
def refresh_expiring_jwts(response):
try:
now = datetime.now()
if request.path == '/api/jwt/check':
return response
exp_timestamp = get_jwt()["exp"]
target_timestamp = datetime.timestamp(now + timedelta(minutes=28))
if target_timestamp > exp_timestamp:
access_token = create_access_token(identity=get_jwt_identity())
set_access_cookies(response, access_token)
return response
except (RuntimeError, KeyError):
# Case where there is not a valid JWT. Just return the original respone
return response

How to mock functionality of boto3 module using pytest

I have a custom module written called sqs.py. The script will do the following:
Get a message from AWS SQS
Get the AWS S3 path to delete
Delete the path
Send a confirmation email to the user
I'm trying to write unit tests for this module that will verify the code will execute as expected and that it will raise exceptions when they do occur.
This means I will need to mock the response from Boto3 calls that I make. My problem is that the code will first establish the SQS client to obtain the message and then a second call to establish the S3 client. I'm not sure how to mock these 2 independent calls and be able to fake a response so I can test my script's functionality. Perhaps my approach is incorrect. At any case, any advice on how to do this properly is appreciated.
Here's how the code looks like:
import boto3
import json
import os
import pprint
import time
import asyncio
import logging
from send_email import send_email
queue_url = 'https://xxxx.queue.amazonaws.com/1234567890/queue'
def shutdown(message):
""" Sends shutdown command to OS """
os.system(f'shutdown +5 "{message}"')
def send_failure_email(email_config: dict, error_message: str):
""" Sends email notification to user with error message attached. """
recipient_name = email_config['recipient_name']
email_config['subject'] = 'Subject: Restore Failed'
email_config['message'] = f'Hello {recipient_name},\n\n' \
+ 'We regret that an error has occurred during the restore process. ' \
+ 'Please try again in a few minutes.\n\n' \
+ f'Error: {error_message}.\n\n' \
try:
send_email(email_config)
except RuntimeError as error_message:
logging.error(f'ERROR: cannot send email to user. {error_message}')
async def restore_s3_objects(s3_client: object, p_bucket_name: str, p_prefix: str):
"""Attempts to restore objects specified by p_bucket_name and p_prefix.
Returns True if restore took place, false otherwise.
"""
is_truncated = True
key_marker = None
key = ''
number_of_items_restored = 0
has_restore_occured = False
logging.info(f'performing restore for {p_bucket_name}/{p_prefix}')
try:
while is_truncated == True:
if not key_marker:
version_list = s3_client.list_object_versions(
Bucket = p_bucket_name,
Prefix = p_prefix)
else:
version_list = s3_client.list_object_versions(
Bucket = p_bucket_name,
Prefix = p_prefix,
KeyMarker = key_marker)
if 'DeleteMarkers' in version_list:
logging.info('found delete markers')
delete_markers = version_list['DeleteMarkers']
for d in delete_markers:
if d['IsLatest'] == True:
key = d['Key']
version_id = d['VersionId']
s3_client.delete_object(
Bucket = p_bucket_name,
Key = key,
VersionId = version_id
)
number_of_items_restored = number_of_items_restored + 1
is_truncated = version_list['IsTruncated']
logging.info(f'is_truncated: {is_truncated}')
if 'NextKeyMarker' in version_list:
key_marker = version_list['NextKeyMarker']
if number_of_items_restored > 0:
has_restore_occured = True
return has_restore_occured
except Exception as error_message:
raise RuntimeError(error_message)
async def main():
if 'AWS_ACCESS_KEY_ID' in os.environ \
and 'AWS_SECRET_ACCESS_KEY' in os.environ \
and os.environ['AWS_ACCESS_KEY_ID'] != '' \
and os.environ['AWS_SECRET_ACCESS_KEY'] != '':
sqs_client = boto3.client(
'sqs',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
verify=False
)
s3_client = boto3.client(
's3',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
verify=False
)
else:
sqs_client = boto3.client(
'sqs',
verify=False,
)
s3_client = boto3.client(
's3',
verify=False,
)
received_message = sqs_client.receive_message(
QueueUrl=queue_url,
AttributeNames=['All'],
VisibilityTimeout=10,
WaitTimeSeconds=20, # Wait up to 20 seconds for a message to arrive
)
if 'Messages' in received_message \
and len(received_message['Messages']) > 0:
# NOTE: Initialize email configuration
receipient_email = 'support#example.com'
username = receipient_email.split('#')[0]
fullname_length = len(username.split('.'))
fullname = f"{username.split('.')[0]}" # Group name / First name only
if (fullname_length == 2): # First name and last name available
fullname = f"{username.split('.')[0]} {username.split('.')[1]}"
fullname = fullname.title()
email_config = {
'destination': receipient_email,
'recipient_name': fullname,
'subject': 'Subject: Restore Complete',
'message': ''
}
try:
receipt_handle = received_message['Messages'][0]['ReceiptHandle']
except Exception as error_message:
logging.error(error_message)
send_failure_email(email_config, error_message)
shutdown(f'{error_message}')
try:
data = received_message['Messages'][0]['Body']
data = json.loads(data)
logging.info('A SQS message for a restore has been received.')
except Exception as error_message:
message = f'Unable to obtain and parse message body. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
bucket = data['bucket']
prefix = data['prefix']
except Exception as error_message:
message = f'Retrieving bucket name and prefix failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
logging.info(f'Initiating restore for path: {bucket}/{prefix}')
restore_was_performed = await asyncio.create_task(restore_s3_objects(s3_client, bucket, prefix))
if restore_was_performed is True:
email_config['message'] = f'Hello {fullname},\n\n' \
+ f'The files in the path \'{bucket}/{prefix}\' have been restored. ' \
send_email(email_config)
logging.info('Restore complete. Shutting down.')
else:
logging.info('Path does not require restore. Shutting down.')
shutdown(f'shutdown +5 "Restore successful! System will shutdown in 5 mins"')
except Exception as error_message:
message = f'File restoration failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
try:
sqs_client.delete_message(
QueueUrl=queue_url,
ReceiptHandle=receipt_handle,
)
except Exception as error_message:
message = f'Deleting restore session from SQS failed. {error_message}'
logging.error(message)
send_failure_email(email_config, message)
shutdown(f'{error_message}')
if __name__ == '__main__':
logging.basicConfig(filename='restore.log',level=logging.INFO)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
The only way I was able to mock Boto3 is rebuilding a small class that represents the actual method structure. This is because Boto3 uses dynamic methods and all the resource level methods are created at runtime.
This might not be industry standard but I wasn't able to get any of the methods I found on the internet to work most of the time and this worked pretty well for me and requires minimal effort (comparing to some of the solutions I found).
class MockClient:
def __init__(self, region_name, aws_access_key_id, aws_secret_access_key):
self.region_name = region_name
self.aws_access_key_id = aws_access_key_id
self.aws_secret_access_key = aws_secret_access_key
self.MockS3 = MockS3()
def client(self, service_name, **kwargs):
return self.MockS3
class MockS3:
def __init__(self):
self.response = None # Test your mock data from S3 here
def list_object_versions(self, **kwargs):
return self.response
class S3TestCase(unittest.TestCase):
def test_restore_s3_objects(self):
# Given
bucket = "testBucket" # Test this to something that somewahat realistic
prefix = "some/prefix" # Test this to something that somewahat realistic
env_vars = mock.patch.dict(os.environ, {"AWS_ACCESS_KEY_ID": "abc",
"AWS_SECRET_ACCESS_KEY": "def"})
env_vars.start()
# initialising the Session can be tricy since it has to be imported from
# the module/file that creates the session on actual code rather than
# where's a Session code is. In this case you might have to import from
# main rather than boto3.
boto3.session.Session = mock.Mock(side_effect=[
MockClient(region_name='eu-west-1',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'])])
s3_client = boto3.client('s3', verify=False)
# When
has_restore_occured = restore_s3_objects(s3_client, bucket, prefix)
# Then
self.assertEqual(has_restore_occured, False) # your expected result set
env_vars.stop()

Trying to create a Gmail API compatible message just does nothing and gives no errors

Gmail decided that SMTP was too simple so they had to block it and setup their own API with all the weird requirements around it. This script which I was trying to use has now become outdated and broken. In an attempt to use it anyway I tried to rewrite it:
"""
Checks stock on specified items at Microcenter store locations,
and sends email notifications when changes are detected.
Applicably, it helps the user obtain rare items during shortages.
"""
from aiohttp import ClientSession
from async_timeout import timeout
from getpass import getpass
from re import search
from smtplib import SMTP
import asyncio
import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
import os
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
class Item:
"""
Class for containing state of individual items; methods update state
by awaiting update().
Item does not need to be directly instantiated; Store will create one
per provided url.
"""
def __init__(self, storeNum, url):
self.storeNum, self.url = storeNum, url
self.sku = self.price = self.stock = None
self.stockChanged = self.priceChanged = False
self.loop = asyncio.get_event_loop()
def __str__(self):
stock = 'in' if self.stock else 'out of'
return f'SKU {self.sku} is {stock} stock for {self.price} at Microcenter {self.storeNum}\n{self.url}\n'
async def pull(self):
async with ClientSession() as session:
async with timeout(10):
async with session.get(self.url, params={'storeSelected': self.storeNum}) as response:
return await response.text()
#staticmethod
def parse_lines(page):
for var in ['SKU', 'inStock', 'productPrice']:
reply = search(f"(?<='{var}':').*?(?=',)", page)
if reply:
yield reply.group()
#staticmethod
def compare(new, old):
return (new != old and old is not None)
async def update(self):
data = tuple(self.parse_lines(await self.pull()))
if not data or any(data) is None:
raise ValueError('Data missing from request or store number invalid')
self.sku, stock, price = int(data[0]), data[1] is 'True', float(data[2])
self.stockChanged, self.priceChanged = self.compare(stock, self.stock), self.compare(price, self.price)
self.stock, self.price = stock, price
class Store:
"""
Periodically checks a given list of urls for stock changes
A store number is required to get accurate stock numbers.
The default store number is set to the North Dallas/Richardson, TX location.
Also required is valid email account information for notifications.
If a recipient address is not provided, the user will be prompted for one.
If the prompt is empty, notifications are sent from the sender
address to itself. Providing an empty string for recipient is a valid
argument to enable loopback operation, as only a value of None
will trigger a prompt.
The default time between checks is 15 minutes. This value should
be at least a few minutes, to avoid being blacklisted by the
server, though this class enforces no such limit. To change the
time period, provide a value in minutes to self.run(minutes).
Setting debug to True enables false positives for testing
"""
def __init__(
self, storeNum=131, sender=None,
recipient=None, debug=True, service=None
):
self.storeNum = storeNum
self.items, self.newInStock, self.totalInStock = set(), 0, 0
self.debug = debug
if not sender:
self.sender = input('Enter sender email address: ').lstrip().rstrip()
else:
self.sender = sender
if recipient is None:
prompted = input('Enter recipient email address (leave blank for loopback): ').lstrip().rstrip()
if not prompted:
self.recipient = self.sender
else:
self.recipient = prompted
else:
self.recipient = self.sender
#Google API BULLSHIT
SCOPES = ['https://www.googleapis.com/auth/gmail.compose','https://www.googleapis.com/auth/gmail.readonly']
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
self.service = build('gmail', 'v1', credentials=creds)
# Call the Gmail API
results = self.service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
print('No labels found.')
else:
print('Labels:')
for label in labels:
print((label['name']))
self.loop = asyncio.get_event_loop()
def __str__(self):
return '\n'.join(item.__str__() for item in self.items)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.loop.close()
#property
def storeNum(self):
return self._storeNum
#storeNum.setter
def storeNum(self, val):
"""
Check to see if value is formatted properly
storeNum must be sent as a string, but should contain an integer.
"""
assert isinstance(val, (int, str)), 'Store number must be an integer or string of integer'
try:
num = int(val)
except:
raise
else:
self._storeNum = str(num)
#property
def sender(self):
return self._sender
#sender.setter
def sender(self, val):
assert val is not None, 'Sender address cannot be empty'
assert isinstance(val, str), 'Must be str'
self._sender = val
def run(self, minutes=5):
run = asyncio.ensure_future(self.check(minutes))
self.loop.run_forever()
async def check(self, minutes=5):
assert isinstance(minutes, (int, float)), 'Minutes must be an integer or float'
seconds = minutes * 60
while True:
print('Checking stock...')
await self.update()
if self.newInStock:
print('New items available')
msg = email_message()
print("message created")
self.send_email(msg)
print("email send attempted")
#if sent:
#print('Recipient notified of stock changes')
else:
print('Stock unchanged')
await asyncio.sleep(seconds)
def add_interactive(self):
entry = True
while entry:
entry = eval(input('Add one or more URLs separated by spaces, or leave blank to complete: '))
try:
urls = entry.split()
except:
if entry and 'http' in entry:
self.add(entry.lstrip().rstrip())
else:
self.add(*urls)
def add(self, *urls):
for url in urls:
assert isinstance(url, str), 'URL must be a string'
if url not in (item.url for item in self.items):
new = Item(self.storeNum, url)
self.loop.run_until_complete(new.update())
self.items.add(new)
def remove(self, *urls):
for url in urls:
assert isinstance(url, str), 'URL must be a string'
self.items = set([item for item in self.items if item.url not in urls])
def email_message(self):
if self.debug:
new = self.items
else:
new = tuple([item for item in self.items if item.stockChanged])
message_text = '\n'.join(item.__str__() for item in new)
print(message_text)
#Create message container
message = MIMEMultipart('alternative') # needed for both plain & HTML (the MIME type is multipart/alternative)
message['Subject'] = self.email_subject()
print("set Subject")
message['From'] = self.sender
print("set sender")
message['To'] = self.recipient
print("set recipient")
#Create the body of the message (a plain-text and an HTML version)
message.attach(MIMEText(message_text, 'plain'))
print("attached plaintext")
message.attach(MIMEText(message_text, 'html'))
print("attached html")
raw_message_no_attachment = base64.urlsafe_b64encode(message.as_bytes())
print("encoded b64")
raw_message_no_attachment = raw_message_no_attachment.decode()
print("decoded raw")
body = {'raw': raw_message_no_attachment}
print("set body")
return body
def email_subject(self):
return f'({self.newInStock} new, {self.totalInStock} total) items in stock at Microcenter {self.storeNum}'
def send_email(self, msgOBJ):
message = msgOBJ
print("message encoded")
try:
message_sent = (self.service.users().messages().send(userId='me', body=message).execute())
message_id = message_sent['id']
# print(attached_file)
print (f'Message sent (without attachment) \n\n Message Id: {message_id}\n\n Message:\n\n {message_text_plain}')
# return body
return True
except errors.HttpError as error:
print (f'An error occurred: {error}')
return False
async def update(self):
for item in self.items:
await item.update()
if self.debug:
self.newInStock = self.totalInStock = len(self.items)
else:
self.newInStock = sum(item.stockChanged for item in self.items)
self.totalInStock = sum(item.stock for item in self.items)
class Clerk(Store):
"""
Further abstraction and automation of Store
Instantiate Clerk with a list of urls as arguments
and an optional store number as a keyword argument.
Clerk exists to be able to start and run a Store in one line.
The user will be prompted for email account information.
"""
def __init__(self, *urls, storeNum=131):
super().__init__(storeNum=storeNum)
if urls:
super().add(*urls)
else:
super().add_interactive()
super().run()
Clerk("https://www.microcenter.com/product/616858/amd-ryzen-9-3950x-35ghz-16-core-am4-boxed-processor", storeNum=155)
I wrote this in a way that is Python 3.6 compatible and Gmail API friendly so it'll actually work. However, upon calling the Store.email_message method (which is supposed to create and return the necessary b64 encoded message object) nothing happens, not one of the prints spaced throughout it is called and no error is returned either. It just stops there.
I initially tried the code from the examples in the Gmail API Documentation, but that didn't work, so then i went searching through the web until I decided to stop with the code I got here (code stolen from their send_Message_without_attachment and create_message_without_attachment functions) and ask for help.
Edit
I followed the advice of the answer I got and changed the email_message function to
def email_message(self):
if self.debug:
new = self.items
else:
new = tuple([item for item in self.items if item.stockChanged])
message_text = '\n'.join(item.__str__() for item in new)
print(message_text)
#Create message container
message = MIMEMultipart('alternative') # needed for both plain & HTML (the MIME type is multipart/alternative)
message['Subject'] = self.email_subject()
message['From'] = self.sender
message['To'] = self.recipient
#Create the body of the message (a plain-text and an HTML version)
message.attach(MIMEText(message_text, 'plain'))
message.attach(MIMEText(message_text, 'html'))
raw_message_no_attachment = urlsafe_b64encode(bytes(message))
raw_message_no_attachment = raw_message_no_attachment.decode()
body = {'raw': raw_message_no_attachment}
return body
That said it still gives no error and doesn't even get to print the message text when it gets to the point where it's called, so I'm still pretty lost.
For the encoding you have to change your import and use like this:
Import:
from base64 import urlsafe_b64encode
Use:
encode = urlsafe_b64encode(bytes(message))
For the scopes using this one is more than enough:
SCOPES = ['https://mail.google.com/']
Remember to delete and renew the token.pickle every time you change the scopes.
Be sure that the API credentials are Ok.

Parse Header in Post Call Python

I am using rest client in my mozilla browser to call an auth service.
When i pass my credentials in Body, i get an "auth-token" . I then set this token in the header in the browser HEADERS tab.
I have to parse this header which i am setting in the browser in my python script as a variable. Further, after getting this value in my script i have to authenticate the token for its validity.
However i am unable to get the tokens value in my script. My auth function is ready. I just have to fetch the token
How should i fetch this token value from the header ??
Code:
def check_authentication(auth):
print "Auth" , auth
chek_auth_url = ("http://10.168.2.161/auth/v/%s" % (auth))
auth = requests.get(chek_auth_url)
if auth.status_code == 200:
return True
I have to pass the token as a paramter in this function and call in this function in main for authentication.
def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, basestring):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
#h['Access-Control-Allow-Headers'] = "Content-Type"
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
#app.route('/test', methods=['POST', 'OPTIONS'])
#crossdomain(origin='*', headers='Content-Type')
def get_storage():
*check_authentication is called here and token is passed as a parameter*
*if token is valid further task i hav to do*
if __name__ == '__main__':
app.run(host='192.168.56.1', port=8080, threaded=True)
Self-Help is the best help..
Finally i found a fix:
The token value is fetched in the variable tokenValue. I can now do my further coding.
tokenValue = request.headers.get("token")
if tokenValue == None:
return "x-auth-token not passed in header, please pass the token."
else:
print "Token passed is", tokenValue

Spyne: Why am I getting empty responses for json requests?

I have a working application that accepts SOAP requests, processes the requests, forwards the SOAP request to an API, processes the response, and then forwards the response to the client.
I'm trying to change this application so that it will be JSON between my application and the client but still use SOAP between API and my application
Now, it can successfully accept JSON requests from client and send/receive SOAP with API. However, all the responses to client are empty.
The only case that I receive a non-empty response is when there are validation errors with my JSON request.
Here are some code that might be relevant
app = Application([MyServer],
MY_NAMESPACE,
in_protocol=JsonDocument(validator='soft'),
out_protocol=JsonDocument())
application_server = csrf_exempt(MyDjangoApplication(app))
definition of MyDjangoApplication
class MyDjangoApplication(DjangoApplication):
def __call__(self, request, *args, **kwargs):
retval = self.HttpResponseObject()
def start_response(status, headers):
# Status is one of spyne.const.http
status, reason = status.split(' ', 1)
retval.status_code = int(status)
for header, value in headers:
retval[header] = value
environ = request.META.copy()
if request.method == 'POST':
response = self.handle_rpc(environ, start_response)
else:
home_path = reverse('proxy:list_method')
uri = MY_ENDPOINT_URL or request.build_absolute_uri(home_path)
# to generate wsdl content
response = self._WsgiApplication__handle_wsdl_request(environ, start_response, uri)
if request.path == home_path and _is_wsdl_request(environ):
fn = None
elif 'method_name' in kwargs:
fn = view_method
else:
fn = list_method
if fn:
return fn(request, app=self, *args, **kwargs)
self.set_response(retval, response)
return retval
Definition of MyServer
class MyServer(ServiceBase):
#rpc(MyTestMethodRequest, Sign, **method(_returns=MyTestMethodResponse))
#check_method()
def TestMethod(ctx, request, signature):
response = {
'Data': "test"
}
return response
Definitions of MyTestMethodRequest, MyTestMethodResponse:
class MyTestMethodRequest(ComplexModel):
__namespace__ = MY_NAMESPACE
MyString = String(encoding=STR_ENCODING)
class MyTestMethodResponse(ComplexModel):
__namespace__ = MY_NAMESPACE
Data = String(encoding=STR_ENCODING)
Definition of check_method:
def check_method(error_handler=None):
def _check_method(func):
method_name = func.__name__
def __check_method(ctx, request, signature, *args, **kwargs):
if hasattr(request, '__dict__'):
request = request.__dict__
if hasattr(signature, '__dict__'):
signature = signature.__dict__
response = func(ctx, request or {}, signature or {}, *args, **kwargs)
# setting output protocol
output_message = generate_out_string(ctx, [response])
return response
__check_method.__name__ = method_name
__check_method.__doc__ = func.__doc__
return __check_method
return _check_method
Definition of generate_out_string:
def generate_out_string(ctx, objects):
ctx.out_protocol = ctx.in_protocol
return _generate_out_string(ctx, objects)
def _generate_out_string(ctx, objects):
protocol = ctx.out_protocol
ctx.out_object = objects
protocol.serialize(ctx, protocol.RESPONSE)
protocol.create_out_string(ctx)
out_string = list(ctx.out_string)
return out_string[0] if out_string else ''
Note: Most of these definitions have been simplified (I have removed lines which I think are not relevant)
Looking at the code you posted, I can't say I understand what good all those additional decorators and modifiers around arguments do.
Removing them should fix all of your problems.
So let:
class MyTestMethodRequest(ComplexModel):
__namespace__ = MY_NAMESPACE
MyString = Unicode
class MyTestMethodResponse(ComplexModel):
__namespace__ = MY_NAMESPACE
Data = Unicode
Assuming you have the following service:
class MyService(ServiceBase):
#rpc(MyTestMethodRequest, Sign, _returns=MyTestMethodResponse)
def TestMethod(ctx, request, signature):
return MyTestMethodResponse(data="test")
You can have:
app_json = Application([MyService],
MY_NAMESPACE,
in_protocol=JsonDocument(validator='soft'),
out_protocol=JsonDocument())
and
app_soap = Application([MyService],
MY_NAMESPACE,
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11())
which in turn you can pass to DjangoApplication as usual.:
app_json_dja = csrf_exempt(DjangoApplication(app_json))
app_soap_dja = csrf_exempt(DjangoApplication(app_soap))
which in turn you can mount in Django's url router.
I hope this helps!

Categories

Resources