SSO example python 2.7 - webapp2, google app engine with simpleauth


(Paul Ceccato) #1

This is based on the django example. I have modified it to work with webapp2 on google app engine, using the simpleauth library for login management.

This decorator is used on get methods which require login. Once the user is logged in these fields will be set with the users details, which will be passed back to discourse

self.current_email,
self.current_id,
self.current_first_name

def simpleauth_login_required(handler_method):
	"""A decorator to require that a user be logged in to access a handler.

	To use it, decorate your get() method like this:


		@simpleauth_login_required
		def get(self):
			user = self.current_user
			self.response.out.write('Hello, ' + user.name())
	"""
	def check_login(self, *args, **kwargs):
		if self.request.method != 'GET':
			self.abort(400, detail='The login_required decorator can only be used for GET requests.')

		if self.logged_in:
			handler_method(self, *args, **kwargs)
		else:
			self.session['original_url'] = self.request.url
			self.render('login.html')

	return check_login

Here is the discourse SSO handler

    import base64
import hmac
import hashlib
import urllib

class DiscourseSSOHandler(BaseRequestHandler):

	@simpleauth_login_required
	def get(self):

		payload = self.request.get('sso')
		signature = self.request.get('sig')

		if None in [payload, signature]:
			self.abort(400, detail='No SSO payload or signature.')
		## Validate the payload

		try:
			payload = urllib.unquote(payload)
			assert 'nonce' in base64.decodestring(payload)
			assert len(payload) > 0
		except AssertionError:
			self.abort(400, detail='Invalid payload..')

		key = secrets.DISCOURSE_SSO_SECRET
		h = hmac.new(key, payload, digestmod=hashlib.sha256)
		this_signature = h.hexdigest()

		if this_signature != signature:
			self.abort(400, detail='Payload does not match signature.')

		## Build the return payload

		params = {
			'nonce': base64.decodestring(payload).split('=')[1],
			'email': self.current_email,
			'external_id': self.current_id,
			'username': self.current_first_name
		}

		return_payload = base64.encodestring(urllib.urlencode(params))
		h = hmac.new(key, return_payload, digestmod=hashlib.sha256)
		query_string = urllib.urlencode({'sso': return_payload, 'sig': h.hexdigest()})

		## Redirect back to Discourse

		url = '{0}/session/sso_login'.format(secrets.DISCOURSE_BASE_URL)
		self.redirect('{0}?{1}'.format(url, query_string))