Skip to content

Instantly share code, notes, and snippets.

@ammaraskar
Created June 20, 2018 05:59
Show Gist options
  • Save ammaraskar/e259bc7d50df354e796e3c0d61137b10 to your computer and use it in GitHub Desktop.
Save ammaraskar/e259bc7d50df354e796e3c0d61137b10 to your computer and use it in GitHub Desktop.
diff -r f40f48714e04 extensions/oic_login.py
--- a/extensions/oic_login.py Fri Jun 15 20:25:08 2018 +0200
+++ b/extensions/oic_login.py Wed Jun 20 01:52:00 2018 -0400
@@ -11,6 +11,7 @@
from roundup.cgi.actions import Action
from roundup.cgi.exceptions import *
from roundup import password, hyperdb
+import logging
try:
from oic.oauth2 import rndstr
@@ -65,28 +66,65 @@
res.append(self.db.oic_session.get('sid'))
return res
+PROVIDER_GOOGLE = 'Google'
+PROVIDER_GITHUB = 'Github'
+PROVIDER_URL_MAP = {
+ 'Github': 'https://github.com/settings/developers',
+ 'Google': 'https://accounts.google.com'
+}
+
+def select_provider(form):
+ if 'provider' in form:
+ return form['provider'].value
+ else:
+ return PROVIDER_GOOGLE
+
class OICMixin:
- # XXX this somehow needs to be generalized if more OIC provider are supported
- provider = "https://accounts.google.com"
- def init_oic(self):
- self.scopes = ["openid","profile","email"]
+ def init_oic(self, provider_name):
+ assert provider_name in PROVIDER_URL_MAP
+ provider = PROVIDER_URL_MAP[provider_name]
+ logging.debug("init_oic: {}".format(provider))
+
+ self.scopes = ["openid", "profile", "email"]
db = SessionStore(self.db)
client = Consumer(db, consumer_config, client_config=client_config)
client.allow['issuer_mismatch'] = True
- client.provider_info = client.provider_config(self.provider)
- providers = self.db.oic_registration.filter(None, {'issuer':self.provider})
+
+ # Github does not support dynamically resolving open id configuration
+ if provider_name == PROVIDER_GITHUB:
+ self.scopes = ["user:email", "read:user"]
+ client.provider_info = {
+ 'authorization_endpoint': 'https://github.com/login/oauth/authorize',
+ 'token_endpoint': 'https://github.com/login/oauth/access_token'
+ }
+ client.handle_provider_config(client.provider_info, 'Github')
+ else:
+ client.provider_info = client.provider_config(provider)
+
+ providers = self.db.oic_registration.filter(None, {'issuer': provider})
assert len(providers) == 1
provider = self.db.oic_registration.getnode(providers[0])
+
client_reg = RegistrationResponse(client_id=provider['client_id'],
client_secret=provider['client_secret'])
client.store_registration_info(client_reg)
return client
+ def redirect_uri(self, provider):
+ redirect_uri = self.base + 'index?@action=oic_authresp'
+ # Avoid breaking existing google callback urls which will not have
+ # provider tagged along
+ if provider != PROVIDER_GOOGLE:
+ redirect_uri += ('&provider=' + provider)
+ return redirect_uri
class OICLogin(Action, OICMixin):
def handle(self):
- client = self.init_oic()
- client.redirect_uris=[self.base+'index?@action=oic_authresp']
+ provider = select_provider(self.client.form)
+ client = self.init_oic(provider)
+
+ redirect_uri = self.redirect_uri(provider)
+ client.redirect_uris = [redirect_uri]
client.state = rndstr()
_nonce = rndstr()
@@ -124,8 +162,11 @@
return
def handle(self):
- client = self.init_oic()
- client.redirect_uris=[self.base+'index?@action=oic_authresp']
+ provider = select_provider(self.client.form)
+ client = self.init_oic(provider)
+
+ redirect_uri = self.redirect_uri(provider)
+ client.redirect_uris = [redirect_uri]
aresp = client.parse_response(AuthorizationResponse, info=self.client.env['QUERY_STRING'],
sformat="urlencoded")
@@ -143,8 +184,13 @@
resp = client.do_access_token_request(scope=self.scopes,
state=aresp["state"],
request_args=args,
- authn_method="client_secret_post"
+ authn_method="client_secret_post",
+ headers={'Accept': 'application/json'}
)
+
+ if provider == PROVIDER_GITHUB:
+ return self.on_github_response(client, resp)
+
try:
id_token = resp['id_token']
except KeyError:
@@ -229,6 +275,58 @@
# confirmation action. Doing so is deferred until need arises
raise ValueError, "Your OpenID Connect account is not supported. Please contact [email protected]"
+ def on_github_response(self, client, response):
+ assert 'access_token' in response
+ token = response['access_token']
+
+ # Grab their info from the github api
+ user_info = client.http_request('https://api.github.com/user',
+ method="GET", headers={
+ 'Authorization': 'token {}'.format(token),
+ 'User-Agent': 'bugs.python.org',
+ 'Accept': 'application/json'
+ })
+
+ assert user_info.status_code == 200
+ user_info = user_info.json()
+
+ github_issuer = PROVIDER_URL_MAP[PROVIDER_GITHUB]
+ github_id = str(user_info['id'])
+
+ oic_account = self.db.oic_account.filter(None, {'issuer': github_issuer, 'subject': github_id})
+ # Existing user
+ if oic_account:
+ # there should be only one user with that ID
+ assert len(oic_account) == 1
+ user = self.db.oic_account.get(oic_account[0], 'user')
+ return self.login(user)
+
+ # Look for unused account name
+ github_name = user_info['login']
+
+ username = github_name
+ suffix = 1
+ while True:
+ user = self.db.user.filter(None, {'username': username})
+ if not user:
+ break
+ suffix += 1
+ username = github_name + str(suffix)
+
+ # generate account
+
+ pw = password.Password(password.generatePassword())
+ user = self.db.user.create(username=username,
+ realname=user_info['name'],
+ github=user_info['login'],
+ password=pw,
+ roles=self.db.config['NEW_WEB_USER_ROLES'],
+ address=user_info['email'])
+ self.db.oic_account.create(user=user, issuer=github_issuer, subject=github_id)
+ # complete login
+ self.db.commit()
+ return self.login(user)
+
class OICDelete(Action):
def handle(self):
if not self.form.has_key('openid'):
diff -r f40f48714e04 html/page.html
--- a/html/page.html Fri Jun 15 20:25:08 2018 +0200
+++ b/html/page.html Wed Jun 20 01:52:00 2018 -0400
@@ -194,6 +194,11 @@
height="16"
src="https://www.google.com/favicon.ico"
alt="Google" title="Google" /></a>
+ <a style="display:inline; width:0; margin: 0" href="index?@action=oic_login&provider=Github">
+ <img hspace="0" vspace="0" width="16" height="16"
+ src="https://www.github.com/favicon.ico"
+ alt="Login with Github" title="Login with Github" />
+ </a>
<a style="display:inline;width:0;margin:0" tal:repeat="prov python:utils.openid_links(request)" tal:attributes="href prov/href">
<img hspace="0" vspace="0" width="16" height="16" tal:attributes="src prov/src;title prov/title;alt prov/alt"/></a>
<input size="10" name="openid_identifier" style="background:url(@@file/openid-16x16.gif)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment