Created
April 25, 2023 19:50
-
-
Save gslin/5e36a177ccbb0cc5af63989fa2b8ce52 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from six.moves.urllib.parse import parse_qs, quote | |
from trac.core import Component, implements | |
from trac.util import hex_entropy | |
from trac.util.datefmt import time_now | |
from trac.util.html import tag | |
from trac.util.translation import _, tag_ | |
from trac.web import IAuthenticator, IRequestHandler | |
from trac.web.chrome import Chrome, INavigationContributor | |
import re | |
import requests | |
class TracGoogleOAuthIntegration(Component): | |
implements(INavigationContributor, IRequestHandler) | |
def __init__(self): | |
super(TracGoogleOAuthIntegration, self).__init__() | |
self.client_id = self.config.get('tracgoogleoauthintegration', 'client_id') | |
self.client_secret = self.config.get('tracgoogleoauthintegration', 'client_secret') | |
self.domain_regex = self.config.get('tracgoogleoauthintegration', 'domain_regex') | |
self.base_url = self.config.get('trac', 'base_url') | |
self.redirect_uri = self.base_url + '/googleoauth/redirect_uri' | |
# INavigationContributor methods | |
def get_active_navigation_item(self, req): | |
return 'login' | |
def get_navigation_items(self, req): | |
if req.is_authenticated: | |
yield ('metanav', 'login', | |
tag_("logged in as %(user)s", | |
user=Chrome(self.env).authorinfo(req, req.authname))) | |
yield ('metanav', 'logout', | |
tag.form( | |
tag.div( | |
tag.button(_("Logout"), name='logout', | |
type='submit'), | |
tag.input(type='hidden', name='__FORM_TOKEN', | |
value=req.form_token) | |
), | |
action=req.href.logout(), method='post', | |
id='logout', class_='trac-logout')) | |
else: | |
yield ('metanav', 'login', | |
tag.a(_("Login"), href='/googleoauth/login')) | |
# IRequestHandler methods | |
def match_request(self, req): | |
if req.path_info == '/googleoauth/login': | |
return True | |
if req.path_info == '/googleoauth/redirect_uri': | |
return True | |
def process_request(self, req): | |
if self.base_url == None: | |
req.send('trac base_url is not set', 'text/plain') | |
return | |
if req.path_info == '/googleoauth/login': | |
# Follow server-side web apps: | |
# https://developers.google.com/identity/protocols/oauth2/web-server | |
url = 'https://accounts.google.com/o/oauth2/v2/auth?' + \ | |
'client_id={}'.format(quote(self.client_id)) + \ | |
'&redirect_uri={}'.format(quote(self.redirect_uri)) + \ | |
'&response_type=code' + \ | |
'&scope=profile+email' | |
req.redirect(url) | |
if req.path_info == '/googleoauth/redirect_uri': | |
# Get access_token | |
url = 'https://oauth2.googleapis.com/token' | |
qsdata = parse_qs(req.query_string) | |
code = qsdata['code'] | |
data = { | |
'code': code, | |
'client_id': self.client_id, | |
'client_secret': self.client_secret, | |
'redirect_uri': self.redirect_uri, | |
'grant_type': 'authorization_code', | |
} | |
res = requests.post(url, data=data) | |
access_token = res.json()['access_token'] | |
# Get userinfo | |
url = 'https://www.googleapis.com/oauth2/v3/userinfo' | |
res = requests.get(url, headers={'Authorization': 'Bearer ' + access_token}) | |
resdata = res.json() | |
if resdata['email_verified'] != True: | |
req.send('email_verified is not True', 'text/plain') | |
return | |
email = resdata['email'] | |
regex = '^(\\S+)@(' + self.domain_regex + ')$' | |
obj = re.search(regex, email) | |
if obj is None: | |
req.send('email and domain_regex are not matched', 'text/plain') | |
return | |
username = obj.group(1) | |
# XXX: reuse LoginModule's cookie format (side effect). | |
cookie = hex_entropy() | |
with self.env.db_transaction as db: | |
db(""" | |
INSERT INTO auth_cookie (cookie, name, ipnr, time) | |
VALUES (%s, %s, %s, %s) | |
""", (cookie, username, req.remote_addr, int(time_now()))) | |
req.authname = username | |
req.outcookie['trac_auth'] = cookie | |
req.outcookie['trac_auth']['expires'] = 31536000 | |
req.outcookie['trac_auth']['httponly'] = True | |
req.outcookie['trac_auth']['path'] = '/' | |
req.outcookie['trac_auth']['secure'] = True | |
req.redirect(self.base_url) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[tracgoogleoauthintegration] | |
client_id = x.apps.googleusercontent.com | |
client_secret = x | |
domain_regex = .* |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment