Building a More Secure Bot

Taylor Hanson

Saturday, June 10th 2017

This tutorial shows how to make an existing Spark bot more secure.

If you are building your first bot, it is recommended you start with this tutorial.

Then come back here once it’s up and running!

 

If someone outside of your organization never interacted with your bot, they can’t just run a simple search to find it. However, if they know or guess the ‘email’ address of your bot, they will be able to add it to a space (formerly called room) and send it messages. The more bots that are out there, the higher the possibility of that happening! To prevent unwanted attention, there are three main ways to control access to your bot:

1. You can setup a space (formerly called room) filter when you create a webhook, which points to your bot application’s server.  If someone adds your bot to a Cisco Spark space, and the space does not fit the filter rule for any of the bot’s webhooks, the bot application server will not receive any messages from Spark.  For large enterprise bots that need access to many different spaces, that might not work out.  That’s okay— you can setup one webhook for your bot, with no filter, to receive all messages from any space to which it belongs, and use the following two methods to secure the bot.

 

2. Filter at the bot’s code level - JSON Payloads are sent from Cisco Spark via HTTP POST to your webserver, which is defined in your webhook (see Handling Requests from Cisco Spark).  When the JSON payload comes in, you can begin your server’s code by checking the incoming message’s data[“personId”] or data[“personEmail”], compare it to a predefined list in your code or database, and handle accordingly.  You can also make a request to Spark with data[“personId”] to check other details, like the organization, to only allow people of a specific org. 

Note: the OrgId that comes in with the webhook JSON Payload is the OrgId of the person that created the webhook.


auth_users = ['tahanson@cisco.com']
my_org_id = "Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi8xZWI2NWZkZi05NjQzLTQxN2YtOTk3NC1hZDcyY2FlMGUxMGY"

@post('/')
def index(request):
 webhook = json.loads(request.body)
 requester = webhook['data']['personEmail']
 if requester != bot_email:
 if requester in auth_users:
            print "Authorized user!"
            #Do stuff
 else:
            print "Unauthorized user!"
            
 #AND/OR check person's org:
 person = sendSparkGET('https://api.ciscospark.com/v1/people/{0}'.format(webhook['data']['personId']))
 if json.loads(person)['orgId'] == my_org_id:
            print "User is a member of Org!"
            #Do stuff
 else:
            print "User it NOT a member of Org!"
return "true"

Note: Above code uses a function defined here.

 

3. Last, but not least, we have the webhook secret.  This should be used to prevent your bot from processing any POST request that does not come from your specific webhook.  This will block any of your other Spark webhooks, or Spark webhooks setup by other users trying to send to your bot’s webserver location.

 

Here is a brief tutorial on using a webhook secret in Python.

 

Everything together, here is a code snippet of all of these features combined:


@post('/')
def index(request):
 raw = request.body
 hashed = hmac.new(key, raw, hashlib.sha1)
 validatedSignature = hashed.hexdigest()
 print 'validatedSignature', validatedSignature
 print 'X-Spark-Signature', request.headers.get('X-Spark-Signature')
 returnVal = ""
 if validatedSignature == request.headers.get('X-Spark-Signature'):
 webhook = json.loads(raw)
 print webhook['data']['id']
 requester = webhook['data']['personEmail']
 if requester != bot_email:
            #get person information, specifically need the person's orgId
            person = sendSparkGET('https://api.ciscospark.com/v1/people/{0}'.format(webhook['data']['personId']))
            if json.loads(person)['orgId'] == my_org_id or requester in auth_users:
                result = sendSparkGET('https://api.ciscospark.com/v1/messages/{0}'.format(webhook['data']['id']))
                result = json.loads(result)
                in_message = result.get('text', '').lower()
                in_message = in_message.replace(bot_name, '')
                #echo the message back to the same room
                sendSparkPOST("https://api.ciscospark.com/v1/messages", {"roomId": webhook['data']['roomId'], "text": in_message})
                returnVal = "success"
 else:
                print "orgId does not match or person not in list of authorized users"
 else:
 print "Secret does not match!"
 return returnVal

 

You can get the full, working example on Github.  You’ll just need to replace your desired orgId, bot name, and token values in the variables at the bottom of the file.  Let us know if you have any questions!

 

- Taylor Hanson, Customer Support Engineer II