Amazon SNS with Python - python

Trying to get started with Amazon SNS. API is very simple REST. I seem to be hung up on the signature part of the call. The API example is:
http://sns.us-east-1.amazonaws.com/
?Subject=My%20first%20message
&TopicArn=arn%3Aaws%3Asns%3Aus-east-1%3A698519295917%3AMy-Topic
&Message=Hello%20world%21
&Action=Publish
&SignatureVersion=2
&SignatureMethod=HmacSHA256
&Timestamp=2010-03-31T12%3A00%3A00.000Z
&AWSAccessKeyId=AKIAJHS4T6XPF7XIURNA
&Signature=9GZysQ4Jpnz%2BHklqM7VFTvEcjR2LIUtn6jW47054xxE%3D
I've been following the API docs for signatures, so I'm trying:
from time import strftime,gmtime,time
import urllib2
import hmac
import hashlib
import base64
import string
def publichSNSMsg(Subject,TopicArn,Message,AWSAccessKeyId,privatekey):
#http://docs.amazonwebservices.com/AWSSimpleQueueService/2008-01-01/SQSDeveloperGuide/
amzsnshost = 'sns.us-east-1.amazonaws.com'
values = {'Subject' : Subject,
'TopicArn' : TopicArn,
'Message' :Message,
'Timestamp' : strftime("%Y-%m-%dT%H:%M:%S.000Z", gmtime(time())),
'AWSAccessKeyId' : AWSAccessKeyId,
'Action' : 'Publish',
'SignatureVersion' : '2',
'SignatureMethod' : 'HmacSHA256',
}
amazquote=lambda v: urllib2.quote(v).replace('%7E','~')
cannqs=string.join(["%s=%s"%(amazquote(key),amazquote(values[key])) for key in sorted(values.keys(),key=str.lower)],'&')
string_to_sign=string.join(["GET",amzsnshost,"/",cannqs],'\n')
sig=base64.encodestring(hmac.new(privatekey,string_to_sign,hashlib.sha1).digest())
querystring = "%s&Signature=%s"%(cannqs,amazquote(sig))
url="http://%s/?%s"%(amzsnshost,querystring)
try:
return urllib2.urlopen(url).read()
except urllib2.HTTPError, exception:
return "Error %s (%s):\n%s"%(exception.code,exception.msg,exception.read())
And getting back:
<ErrorResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<Error>
<Type>Sender</Type>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message>
</Error>
<RequestId>8d6e5a41-dafb-11df-ac33-f981dc4e6c50</RequestId>
</ErrorResponse>
Any ideas?

Aw, it was simple!
Keys in the query string were supposed to be byte-order sorted, not case-insensitive sorted (that was for version 1 signatures).
Slightly updated (and now correct) code is available on gist.

I found this example so useful that I rewrote it in C#. Since AWS does not have a WP7 library for SNS, perhaps this will be of help to someone: https://gist.github.com/2705156

I would suggest you to use boto3 for SNS. The documentation can be found at http://boto3.readthedocs.io/en/latest/reference/services/sns.html. The following code snippet can be used for fetching signature as JSON.
import boto3
from django.views.decorators.csrf import csrf_exempt
topic_arn = <your topic>
client = boto3.client('sns',region_name="us-west-2")
#Call this to publish
def sns_publish(arg1,arg2):
response = client.publish(
TopicArn=topic_arn,
Message=arg2,
Subject=arg2
)
#Function to subscribe to topic
#csrf_exempt
def sns_parse(request):
print request.body.decode('utf-8')
if request.method == "POST":
response = json.loads(request.body.decode('utf-8'))
type = response.get('Type')
if type == 'Subscription':
print(type)
return("Subscription URL: "+<Url obtained>)
elif type == 'Notification':
return HttpResponse(type.get('Signature'))
else:
return HttpResponse('Not a POST object')
This is just a template code though, but yeah, boto3 is indeed helpful.

Related

Problem decoding JWT token with public key from Gravitee in Python

I'm trying to decode a Gravitee JWT Token using public key. I tested already PyJWT, authlib, python-jose and jwcrypto libraries and review a lot of posts on this page but I get the same error in all of them and I could not fix the problem.
Error:
('Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).', [_OpenSSLErrorWithText(code=151584876, lib=9, reason=108, reason_text=b'error:0909006C:PEM routines:get_name:no start line')])
Firts of all I get the public key following Gravitee instructions:
https://docs.gravitee.io/am/current/am_userguide_create_certificate.html
Some info from https://jwt.io about my token:
HEADER:ALGORITHM & TOKEN TYPE
{
"kid": "default",
"alg": "RS256"
}
Python packeges versions:
PyJWT==2.3.0 (also tested with 2.1.0)
cryptography==36.0.0 (some posts suggests is required)
My code:
from rest_framework import permissions
from rest_framework.exceptions import APIException
from django.conf import settings
import jwt
class TokenNotValid(APIException):
status_code = 403
default_detail = "Invalid or absent JWT token field."
class NoAuthHeader(APIException):
status_code = 403
default_detail = "Absent 'Authorization' header."
class ValidJWTPermission(permissions.BasePermission):
"""
Global permission check for JWT token.
"""
def _get_pubkey(self):
key = """-----BEGIN PUBLIC KEY-----\n""" + settings.GRAVITEE_PUBLIC_KEY + """\n-----END PUBLIC KEY-----"""
return key
def has_permission(self, request, view):
auth_header = request.META.get('HTTP_AUTHORIZATION')
# print("Received header:")
# print(auth_header)
if auth_header is None:
raise NoAuthHeader
try:
token = auth_header.split()[1]
# print("Encoded Token:")
# print(token)
public_key = self._get_pubkey()
print(public_key)
claims = jwt.decode(token, key=public_key, algorithms=['RS256'])
claims.validate()
except Exception as e:
print(e)
raise TokenNotValid
# print("Decoded token:")
# print(dec_token)
return True
I tested also encoding the key like key.encode() and key.encode('ascii') or composing the key with "BEGIN RSA PUBLIC KEY" instead of "BEGIN PUBLIC KEY" and anything works for me. Always I have the same error.
Depending on the library, you often wants the key in JWK format as shown in this link:
https://demo.identityserver.io/.well-known/openid-configuration/jwks
This JSON Web Key (JWK) format is a standard for representing keys when you deal with tokens and perhaps you library wants it in that format too?

Decode Firebase JWT in Python using PyJWT

I have written the following code :
def check_token(token):
response = requests.get("https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com")
key_list = response.json()
decoded_token = jwt.decode(token, key=key_list, algorithms=["RS256"])
print(f"Decoded token : {decoded_token}")
I am trying to decode the token provided by firebase client side to verify it server-side.
The above code is throwing the following exception :
TypeError: Expecting a PEM-formatted key.
I have tried to not pass a list to the jwt.decode method, only the key content and i have a bigger error that the library could not deserialize the Key.
I was following this answer but i am getting this error.
Is it a requests conversion problem ? What am i doing wrong ?
The 2nd parameter key in decode() seems to take a string value instead of list. The Google API request returns a dict/map containing multiple keys. The flow goes like:
Fetch public keys from the Google API endpoint
Then read headers without validation to get the kid claim then use it to get appropriate key from that dict
That is a X.509 Certificate and not the public key as in this answer so you need to get public key from that.
The following function worked for me:
import jwt
import requests
from cryptography.hazmat.backends import default_backend
from cryptography import x509
def check_token(token):
n_decoded = jwt.get_unverified_header(token)
kid_claim = n_decoded["kid"]
response = requests.get("https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com")
x509_key = response.json()[kid_claim]
key = x509.load_pem_x509_certificate(x509_key.encode('utf-8'), backend=default_backend())
public_key = key.public_key()
decoded_token = jwt.decode(token, public_key, ["RS256"], options=None, audience="<FIREBASE_PROJECT_ID>")
print(f"Decoded token : {decoded_token}")
check_token("FIREBASE_ID_TOKEN")

How to validate Slack API request?

I've a slack app that is sending to a service written in typescript that is forwarding the message to my python script where I'm trying to validate the request. However, for some reason, the validation always fails.
The typescript relevant code:
const rp = require('request-promise');
var qs = require('querystring')
export const handler = async (event: any, context: Context, callback: Callback): Promise<any> => {
const options = {
method: method,
uri: some_url,
body: qs.parse(event.body),
headers: {
signature: event.headers['X-Slack-Signature'],
timestamp: event.headers['X-Slack-Request-Timestamp']
},
json: true
};
return rp(options);
The python code (based on this article) :
def authenticate_message(self, request: Request) -> bool:
slack_signing_secret = bytes(SLACK_SIGNING_SECRET, 'utf-8')
slack_signature = request.headers['signature']
slack_timestamp = request.headers['timestamp']
request_body = json.loads(request.body)['payload']
basestring = f"v0:{slack_timestamp}:{request_body}".encode('utf-8')
my_signature = 'v0=' + hmac.new(slack_signing_secret, basestring, hashlib.sha256).hexdigest()
return hmac.compare_digest(my_signature, slack_signature))
I'm pretty sure the issue is the way I'm taking the body but tried several options and still no luck.
Any ideas?
Thanks,
Nir.
I had the same issue. My solution was to parse the payload to replace '/' by %2F and ':' by %3A. It's not explicit in the Slack doc but if you see the example, that's how it's shown:
'v0:1531420618:token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c'
You see command and response_url are parsed.
I managed to get this working in Python. I see you ask in Typescript, but I hope this python script helps:
#app.route('/slack-validation', methods=['GET', 'POST'])
def slack_secutiry():
headers = request.headers
timestamp = request.headers['X-Slack-Request-Timestamp']
slack_payload = request.form
dict_slack = slack_payload.to_dict()
### This is the key that solved the issue for me, where urllib.parse.quote(val, safe='')] ###
payload= "&".join(['='.join([key, urllib.parse.quote(val, safe='')]) for key, val in dict_slack.items()])
### compose the message:
sig_basestring = 'v0:' + timestamp + ':' + payload
sig_basestring = sig_basestring.encode('utf-8')
### secret
signing_secret = slack_signing_secret.encode('utf-8') # I had an env variable declared with slack_signing_secret
my_signature = 'v0=' + hmac.new(signing_secret, sig_basestring, hashlib.sha256).hexdigest()
print('my signature: ')
print(my_signature)
return '', 200
It might be useful for you to check how the request validation feature is implemented in the Bolt framework:
https://github.com/slackapi/bolt-python/blob/4e0709f0578080833f9aeab984a778be81a30178/slack_bolt/middleware/request_verification/request_verification.py
Note that it is implemented as a middleware, enabled by default when you instantiate the app (see attribute request_verification_enabled).
You can inspect this behaviour and/or change it if you want to validate the requests manually:
app = App(
token=SLACK_BOT_TOKEN,
signing_secret=SLACK_SIGNING_SECRET,
request_verification_enabled=False
)
The following solution solves the problem of verification of signing secret of slack
#!/usr/bin/env python3
import hashlib
import hmac
import base64
def verify_slack_request(event: dict, slack_signing_secret: str) -> bool:
"""Verify slack requests.
Borrowed from https://janikarhunen.fi/verify-slack-requests-in-aws-lambda-and-python.html
- Removed optional args
- Checks isBase64Encoded
:param event: standard event handler
:param slack_signing_secret: slack secret for the slash command
:return: True if verification worked
"""
slack_signature = event['headers']['x-slack-signature']
slack_time = event['headers']['x-slack-request-timestamp']
body = event['body']
if event['isBase64Encoded']:
body = base64.b64decode(body).decode("utf-8")
""" Form the basestring as stated in the Slack API docs. We need to make a bytestring"""
base_string = f'v0:{slack_time}:{body}'.encode('utf-8')
""" Make the Signing Secret a bytestring too. """
slack_signing_secret = bytes(slack_signing_secret, 'utf-8')
""" Create a new HMAC 'signature', and return the string presentation."""
my_signature = 'v0=' + hmac.new(
slack_signing_secret, base_string, hashlib.sha256
).hexdigest()
''' Compare the the Slack provided signature to ours.
If they are equal, the request should be verified successfully.
Log the unsuccessful requests for further analysis
(along with another relevant info about the request).'''
result = hmac.compare_digest(my_signature, slack_signature)
if not result:
logger.error('Verification failed. my_signature: ')
logger.error(f'{my_signature} != {slack_signature}')
return result
if __name__ == '__main__':
# add correct params here
print(verify_slack_request({}, None))
Borrowed From:
https://gist.github.com/nitrocode/288bb104893698011720d108e9841b1f
Credits: https://gist.github.com/nitrocode

Get user message on flask-assistant

I use flask-assistant on python 3 with dilaogflow as a webhook. I looked at the official documentation and I don't find how to get the user message ("queryText" on dialogflow json request).
I tried this with no success:
# -*- coding: utf-8 -*-
from flask import Flask
from flask_assistant import Assistant, ask, tell, context_manager, event
project_id='myproject_id'
app = Flask(__name__)
assist = Assistant(app, route='/', project_id = project_id)
#assist.action('Default Fallback Intent')
def manage_fallback(queryText):
print('*'*40,queryText)
speech='Running'
return tell(speech)
if __name__ == '__main__':
app.run(debug=True)
The print of queryText always return None, but when I inspect on the ngrok web interface (http://127.0.0.1:4040) , I can see the request.
And I want to know how canI get the user message from flask-assistant?
I also asked about this question on github and get the answer, so I will share for the others:
You can get the query text from the flask-assistant request object.
from flask_assistant import request
...
...
#assist.action('Default Fallback Intent')
def manage_fallback():
user_query = request['queryResult']['queryText']
speech = "You said " + user_query
return tell(speech)
The reason the value of queryText expected by your manage_fallback function is None is because the parameter name must match the entity type expected by the intent.
Parameters are used to receive parsed entities for an intent, not the full user query.

How to process JSON in Flask?

I have asked a few questions about this before, but still haven't solved my problem.
I am trying to allow Salesforce to remotely send commands to a Raspberry Pi via JSON (REST API). The Raspberry Pi controls the power of some RF Plugs via an RF Transmitter called a TellStick. This is all setup, and I can use Python to send these commands. All I need to do now is make the Pi accept JSON, then work out how to send the commands from Salesforce.
Someone kindly forked my repo on GitHub, and provided me with some code which should make it work. But unfortunately it still isn't working.
Here is the previous question: How to accept a JSON POST?
And here is the forked repo: https://github.com/bfagundez/RemotePiControl/blob/master/power.py
What do I need to do? I have sent test JSON messages n the Postman extension and in cURL but keep getting errors.
I just want to be able to send various variables, and let the script work the rest out.
I can currently post to a .py script I have with some URL variables, so /python.py?power=on&device=1&time=10&pass=whatever and it figures it out. Surely there's a simple way to send this in JSON?
Here is the power.py code:
# add flask here
from flask import Flask
app = Flask(__name__)
app.debug = True
# keep your code
import time
import cgi
from tellcore.telldus import TelldusCore
core = TelldusCore()
devices = core.devices()
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<deviceId>",methods=['POST'])
def powerOnDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
# define a "power OFF api endpoint"
#app.route("/API/v1.0/power-off/<deviceId>",methods=['POST'])
def powerOffDevice(deviceId):
payload = {}
#get the device by id somehow
device = devices[deviceId]
try:
device.turn_off()
payload['success'] = True
return payload
except:
payload['success'] = False
# add an exception description here
return payload
app.run()
Your deviceID variable is a string, not an integer; it contains a '1' digit, but that's not yet an integer.
You can either convert it explicitly:
device = devices[int(deviceId)]
or tell Flask you wanted an integer parameter in the route:
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
where the int: part is a URL route converter.
Your views should return a response object, a string or a tuple instead of a dictionary (as you do now), see About Responses. If you wanted to return JSON, use the flask.json.jsonify() function:
# define a "power ON api endpoint"
#app.route("/API/v1.0/power-on/<int:deviceId>", methods=['POST'])
def powerOnDevice(deviceId):
device = devices[deviceId]
# get some extra parameters
# let's say how long to stay on
params = request.get_json()
try:
device.turn_on()
return jsonify(success=True)
except SomeSpecificException as exc:
return jsonify(success=False, exception=str(exc))
where I also altered the exception handler to handle a specific exception only; try to avoid Pokemon exception handling; do not try to catch them all!
To retrieve the Json Post values you must use request.json
if request.json and 'email' in request.json:
request.json['email']

Categories