mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-21 07:22:14 +00:00
Proof of concept discourse authentication
This commit is contained in:
0
RIGS/discourse/__init__.py
Normal file
0
RIGS/discourse/__init__.py
Normal file
89
RIGS/discourse/discourse.py
Normal file
89
RIGS/discourse/discourse.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
from social.backends.base import BaseAuth
|
||||
from social.exceptions import AuthException
|
||||
|
||||
from .sso import DiscourseSSO
|
||||
|
||||
|
||||
class DiscourseAssociation(object):
|
||||
""" Use Association model to save the nonce by force. """
|
||||
|
||||
def __init__(self, handle, secret='', issued=0, lifetime=0, assoc_type=''):
|
||||
self.handle = handle # as nonce
|
||||
self.secret = secret.encode() # not use
|
||||
self.issued = issued # not use
|
||||
self.lifetime = lifetime # not use
|
||||
self.assoc_type = assoc_type # as state
|
||||
|
||||
|
||||
class DiscourseAuth(BaseAuth):
|
||||
"""Discourse authentication backend"""
|
||||
name = 'discourse'
|
||||
secret = os.environ['DISCOURSE_SSO_SECRET']
|
||||
host = os.environ['DISCOURSE_HOST']
|
||||
|
||||
EXTRA_DATA = [
|
||||
('username', 'username'),
|
||||
('email', 'email'),
|
||||
('external_id', 'external_id')
|
||||
]
|
||||
|
||||
sso = DiscourseSSO(secret)
|
||||
|
||||
def get_and_store_nonce(self, url):
|
||||
# Create a nonce
|
||||
nonce = self.strategy.random_string(64)
|
||||
# Store the nonce
|
||||
association = DiscourseAssociation(nonce)
|
||||
self.strategy.storage.association.store(url, association)
|
||||
return nonce
|
||||
|
||||
def get_nonce(self, nonce):
|
||||
try:
|
||||
return self.strategy.storage.association.get(
|
||||
server_url=self.host,
|
||||
handle=nonce
|
||||
)[0]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def remove_nonce(self, nonce_id):
|
||||
self.strategy.storage.association.remove([nonce_id])
|
||||
|
||||
def get_user_id(self, details, response):
|
||||
"""Return current user id."""
|
||||
|
||||
return int(response['external_id'])
|
||||
|
||||
def get_user_details(self, response):
|
||||
"""Return user basic information (id and email only)."""
|
||||
|
||||
return {'username': response['username'],
|
||||
'email': response['email'],
|
||||
'fullname': response['name'].replace('+', ' ') if 'name' in response else '',
|
||||
'first_name': '',
|
||||
'last_name': ''}
|
||||
|
||||
def auth_url(self):
|
||||
"""Build and return complete URL."""
|
||||
nonce = self.get_and_store_nonce(self.host)
|
||||
|
||||
return self.host + self.sso.build_login_URL(nonce, self.redirect_uri)
|
||||
|
||||
def auth_complete(self, *args, **kwargs):
|
||||
"""Completes login process, must return user instance."""
|
||||
|
||||
if not self.sso.validate(self.data['sso'], self.data['sig']):
|
||||
raise Exception("Someone wants to hack us!")
|
||||
|
||||
nonce = self.sso.get_nonce(self.data['sso'])
|
||||
nonce_obj = self.get_nonce(nonce)
|
||||
if nonce_obj:
|
||||
self.remove_nonce(nonce_obj.id)
|
||||
else:
|
||||
raise Exception("Nonce does not match!")
|
||||
|
||||
kwargs.update({'response': self.sso.get_data(
|
||||
self.data['sso']), 'backend': self})
|
||||
return self.strategy.authenticate(*args, **kwargs)
|
||||
46
RIGS/discourse/sso.py
Normal file
46
RIGS/discourse/sso.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import urllib
|
||||
from hashlib import sha256
|
||||
import hmac
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
|
||||
class DiscourseSSO:
|
||||
def __init__(self, secret_key):
|
||||
self.__secret_key = secret_key
|
||||
|
||||
def validate(self, payload, sig):
|
||||
payload = urllib.unquote(payload)
|
||||
computed_sig = hmac.new(
|
||||
self.__secret_key.encode(),
|
||||
payload.encode(),
|
||||
sha256
|
||||
).hexdigest()
|
||||
print(type(computed_sig), type(sig))
|
||||
return hmac.compare_digest(unicode(computed_sig), sig)
|
||||
|
||||
def get_nonce(self, payload):
|
||||
payload = b64decode(urllib.unquote(payload)).decode()
|
||||
d = dict(nonce.split("=") for nonce in payload.split('&'))
|
||||
|
||||
if 'nonce' in d and d['nonce'] != '':
|
||||
return d['nonce']
|
||||
else:
|
||||
raise Exception("Nonce could not be found in payload")
|
||||
|
||||
def get_data(self, payload):
|
||||
payload = urllib.unquote(b64decode(urllib.unquote(payload)).decode())
|
||||
d = dict(data.split("=") for data in payload.split('&'))
|
||||
|
||||
return d
|
||||
|
||||
def build_login_URL(self, nonce, redirect_uri):
|
||||
data = {
|
||||
'nonce': nonce,
|
||||
'return_sso_url': redirect_uri
|
||||
}
|
||||
|
||||
payload = urllib.urlencode(data)
|
||||
payload = b64encode(payload.encode())
|
||||
sig = hmac.new(self.__secret_key.encode(), payload, sha256).hexdigest()
|
||||
|
||||
return '/session/sso_provider?' + urllib.urlencode({'sso': payload, 'sig': sig})
|
||||
Reference in New Issue
Block a user