In the beginning of December, our CEO, Rowan Trollope, and CTO, Jonathan Rosenberg, launched our Spark developer platform for collaboration at the Collaboration Summit. Over Christmas, I played around a bit to see what we expose publicly, and thought I’d share a bit about what I did. The guys from the Tropo acquisition have been working on this, and they have really done a great job! If you haven’t started using Spark to collaborate with colleagues (great for family and friends as well), then you sign up for a free account at https://web.ciscospark.com. And you can of course make audio and video calls and conferences! 😀
Basically, what I wanted to share is how to build a python app using Google’s Appengine to connect to the Spark APIs and start interacting with Spark rooms and content on behalf of any user authorising such access. It’s fairly basic, it will request authorisation, get the necessary access token, and retrieve basic information about the user who granted access, including the avatar image, and then just show that as a response. You can see the app for yourself at https://gregers-sparktest.appspot.com/sparktest. (if you are already logged in as a spark user, you will not be asked to log in again, even if you have a corporate single-sign on account). Note that I have used my picture to show the authenticity of the app, and it is requesting access to everything, but no access token is stored, so don’t worry… However, I could of course any time change the app to store the access tokens, so if you are worried about that, don’t grant access 😉
Step 1 – Register a Cisco Spark account (if you haven’t already)
Go to https://web.ciscospark.com. Just type in your email address and press Next. If you have not already registered, you will be sent an email to confirm it.
One note on consumer vs corporate single-sign on accounts: The APIs are based on consumer-account authentication (and not single-sign on), so if you are not signed in, you will be redirected to a sign-in page that does not give you corporate single-sign on. However, the authorisation mechanisms are the same, so if you have already logged into your corporate SSO account (e.g. cisco.com account), you will be allowed to access everything with your corporate identity. Just be aware of this! Another note Feb 10, 2016: looks like we have just updated the APIs OAuth to use the same login redirects as web.ciscospark.com. You will thus get a prompt for your email address and then the login page will either be the corporate SSO login or the consumer login, so you don’t have to login with your SSO account before you try this demo. However, if you mix a consumer and a corporate SSO account, you may sometimes get an SSO login failed. Clearing cookies (history) will fix this.
Step 2 – Register a Google Appengine account and deploy an example app
Go to https://cloud.google.com/appengine, and log in using your google account. You can go to https://cloud.google.com/appengine/docs to get access to the quick start, however, the tutorial I recommend is here. It covers a lot things in a guestbook app you don’t really need to understand to get started, but it’s a simple way to deploy your first app.
Once you have deployed your first python 2.7 app, you can either just re-use that app (and replace the code) or create a new project at https://console.cloud.google.com. You then add the app with the same name in Google AppEngineLauncher on your computer and a new local directory will be created for you. Also remember, you will need the public URL created for your Appengine App in the next step.
Tip: If you right-click the app in AppEngineLauncher and choose Info…, you can set the debug level to info in the Extra Flags field. Just add –log_level=info there and press Update. The app will then log what happens.
Step 3 – Register a Spark app
Go to https://developer.ciscospark.com/apps.html and add a new app. When you register information here, remember that the information will be presented to people who will try to access your app (when they grant access to your app on their Spark account). The name, the icon URL, as well as the scopes you request will be presented to the user when granting access to your app. Choose whatever you like, including the scopes (not really relevant for this demo), as long as you choose one or more scopes.
The URI is important though. This is the callback URL that Spark will use as extra security to make sure that it redirects back to your app. Use the public URL (preferably with https) for your newly created Appengine app and add /sparktest at the end, the full URL for my app is https://gregers-sparktest.appspot.com/sparktest.
Step 4 – Make your python appengine app and deploy it to appengine
You need five files in your appengine app directory: app.yaml, sparktest.py, sparktest.html, and in a sub.folder called spark, sparks.py and __init__.py (two underscores before and after init).
The app.yaml file is the gatekeeper into your app. This is where you define the URL paths (like sparktest) that your app will respond to and which python file will be triggered to respond to a request. We just define one URL path, /sparktest, and directs it to sparktest.py (here written as sparktest.application).
Here is the app.yaml file:
application: your-appengine-app-name version: 1 runtime: python27 api_version: 1 threadsafe: true handlers: - url: /sparktest script: sparktest.application secure: always libraries: - name: webapp2 version: "2.5.2"
Note that I have added secure: always to the /sparktest handler here to make sure that you don’t get an http request to it by accident (it will expose the access token). Remember to replace the your-appengine-app-name with the correct appname you have chosen when you created your Appengine app.
Secondly, we need the spark library I created. Create a new folder in your app directory called spark. You need two files in this directory. The first is a file you call __init__.py (two underscores init two underscores) and put just one line into it:
__all__ = ["sparks"]
This is the init file a python library, so that the sparks object will be imported.
Then, add a new file called sparks.py, here is the code:
__all__ = [ 'sparks', ] import urllib import logging import json from google.appengine.api import urlfetch class sparks(): def __init__(self): self.client_id=“your_client_id” self.client_secret=“your_client_secret” self.redirect_uri="https://your-appengine-app-name.appspot.com/sparktest" self.response_type="code" self.grant_type="authorization_code" self.refresh_type="refresh_token" self.scope="spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_write spark:messages_read" self.auth_uri="https://api.ciscospark.com/v1/authorize" self.token_uri="https://api.ciscospark.com/v1/access_token" self.me_uri="https://api.ciscospark.com/v1/people/me" def postRequest(self, url, token, params): data = urllib.urlencode(params) logging.info('Spark POST request with payload: '+data) if token: headers = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Bearer '+token, } else: headers = {'Content-Type': 'application/x-www-form-urlencoded'} response = urlfetch.fetch(url=url, payload=data, method=urlfetch.POST, headers=headers) if response.status_code != 200: logging.info('Error when sending POST request: '+str(response.status_code)+response.content) return None logging.info('Spark POST response JSON:'+response.content) return json.loads(response.content) def getRequest(self, url, token): response = urlfetch.fetch(url=url, payload=None, method=urlfetch.GET, headers={'Content-Type': 'application/json', 'Authorization': 'Bearer '+token} ) if response.status_code != 200: logging.info('Error when sending GET request to Spark: '+str(response.status_code)+response.content) return None return json.loads(response.content) def oauthRedirectURI(self, state=''): params = { 'response_type': self.response_type, 'client_id': self.client_id, 'redirect_uri': self.redirect_uri, 'scope': self.scope, 'state': state, } uri=self.auth_uri+"?"+urllib.urlencode(params) logging.info('OAuth redirect with url: '+uri+' and state:'+state) return uri def oauthRequestToken(self, code): if not code: return None params = { 'grant_type': self.grant_type, 'client_id': self.client_id, 'client_secret': self.client_secret, 'code': code, 'redirect_uri': self.redirect_uri, } return self.postRequest(url = self.token_uri, token = None, params = params) def oauthRefreshToken(self, refresh_token): if not refresh_token: return None params = { 'grant_type': self.refresh_type, 'client_id': self.client_id, 'client_secret': self.client_secret, 'refresh_token': refresh_token, } return self.postRequest(url = self.token_uri, token = None, params = params)
Ok, there are three lines you need to edit and replace with your app’s data:
self.client_id=“your_client_id” self.client_secret=“your_client_secret” self.redirect_uri="https://your-appengine-app-name.appspot.com/sparktest"
The client id and secrets can be found at developer.ciscospark.com once you have created your Spark application. This is for Spark to recognise your application. Then you need to change the redirect uri to the URL of your Appengine app. This URL should also match the URL you used for your app when you created the Spark application. If not, go to developer.ciscospark.com and edit your application to update the URL.
You now need the actual code that will be executed when you go to https://your-appengine-app-name.appspot.com/sparktest. This is the sparktest.py file (put it in the root directory of your Appengine app):
#!/usr/bin/env python import webapp2 import os from spark import sparks from google.appengine.ext.webapp import template class MainPage(webapp2.RequestHandler): def get(self): spark=sparks.sparks() if self.request.get('code'): code=self.request.get('code') else: self.redirect(spark.oauthRedirectURI('SparkTest')) return result = spark.oauthRequestToken(code) if not result: self.response.set_status(502, "OAuth Token Request/Refresh Failed") return me = spark.getRequest(spark.me_uri, result['access_token']) if 'avatar' not in me: me['avatar'] = "http://www.in3dgallery.com/img/no_avatar.png" template_values = { 'displayName': me['displayName'], 'token' : result['access_token'], 'email' : me['emails'][0], 'avatar' : me['avatar'], } path = os.path.join(os.path.dirname(__file__), 'sparktest.html') self.response.write(template.render(path, template_values)) application = webapp2.WSGIApplication([ webapp2.Route(r'/sparktest', MainPage, name='MainPage'), ], debug=True)
Look at the end of the file. The WSGIApplication call is the magic that registers your MainPage class as the handler for requests to /sparktest. This class has only one method, get(), which will be triggered when an http GET (regular request for that web page) is received.
We are nearly there. The only remaining thing you need is the sparktest.html file. This file is a template to create the html that you get back at the end. Here is sparktest.html:
<html> <body> <img src="{{ avatar }}" width=120 height=120><br> {{ displayName }} with email {{ email }} has token {{ token }} </body> </html>
Step 5 – Deploy your application
In Google AppEngineLauncher, select Deploy and wait for it to deploy.
Step 6 – Try it!
Go to https://your-appengine-app-name.appspot.com/sparktest either after having logged in at web.ciscospark.com (if you are a corporate SSO user) or if you have a consumer account, it doesn’t matter. If you haven’t logged in, you should be asked to login. Then you get an authorisation request from your app and finally the output generated from the html template.
If something goes wrong, you can go to https://console.developers.google.com/appengine. At the bottom of this page, there is a section called Current load. You will see a list of URI requests that have come in to your app. Click on /sparktest and you will see the logs. If you click the arrow to the left of the request to expand, you will see the logging from your app.
Here are snapshots that I see when I try it:
And after I have accepted:
Note that I have not set an avatar for this account, so the app is using the default that is hardcoded in the code.