From 6c2495ce15c6bd82cd3eff285932c3fdbfef73a4 Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Nov 30 2017 12:32:38 +0000 Subject: [PATCH 1/4] Implement Google OAuth authentication --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 149e86b..5535d65 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -16,15 +16,18 @@ __version__ = '3.11.1' __api_version__ = '0.20' +import base64 # noqa: E402 import datetime # noqa: E402 import gc # noqa: E402 import logging # noqa: E402 import logging.config # noqa: E402 import os # noqa: E402 import re # noqa: E402 +import time # noqa: E402 import urlparse # noqa: E402 import flask # noqa: E402 +import munch # noqa: E402 import pygit2 # noqa: E402 import werkzeug # noqa: E402 from functools import wraps # noqa: E402 @@ -100,88 +103,154 @@ import pagure.login_forms # noqa: E402 import pagure.mail_logging # noqa: E402 import pagure.proxy # noqa: E402 +def set_user(user): + if flask.g.fas_user.username is None: + flask.flash( + 'It looks like your OpenID provider did not provide an ' + 'username we could retrieve, username being needed we cannot ' + 'go further.', 'error') + logout() + return + + flask.session['_new_user'] = False + if not pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username): + flask.session['_new_user'] = True + + try: + pagure.lib.set_up_user( + session=SESSION, + username=flask.g.fas_user.username, + fullname=flask.g.fas_user.fullname, + default_email=flask.g.fas_user.email, + ssh_key=flask.g.fas_user.get('ssh_key'), + keydir=APP.config.get('GITOLITE_KEYDIR', None), + ) + + # If groups are managed outside pagure, set up the user at login + if not APP.config.get('ENABLE_GROUP_MNGT', False): + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + old_groups = set(user.groups) + fas_groups = set(flask.g.fas_user.groups) + # Add the new groups + for group in fas_groups - old_groups: + groupobj = None + if group: + groupobj = pagure.lib.search_groups( + SESSION, group_name=group) + if groupobj: + try: + pagure.lib.add_user_to_group( + session=SESSION, + username=flask.g.fas_user.username, + group=groupobj, + user=flask.g.fas_user.username, + is_admin=is_admin(), + from_external=True, + ) + except pagure.exceptions.PagureException as err: + APP.logger.error(err) + # Remove the old groups + for group in old_groups - fas_groups: + if group: + try: + pagure.lib.delete_user_of_group( + session=SESSION, + username=flask.g.fas_user.username, + groupname=group, + user=flask.g.fas_user.username, + is_admin=is_admin(), + force=True, + from_external=True, + ) + except pagure.exceptions.PagureException as err: + APP.logger.error(err) + + SESSION.commit() + except SQLAlchemyError as err: + SESSION.rollback() + APP.logger.exception(err) + flask.flash( + 'Could not set up you as a user properly, please contact ' + 'an admin', 'error') + # Ensure the user is logged out if we cannot set them up + # correctly + logout() + # Only import flask_fas_openid if it is needed if APP.config.get('PAGURE_AUTH', None) in ['fas', 'openid']: from flask_fas_openid import FAS FAS = FAS(APP) @FAS.postlogin - def set_user(return_url): + def set_user_fas(return_url): ''' After login method. ''' - if flask.g.fas_user.username is None: - flask.flash( - 'It looks like your OpenID provider did not provide an ' - 'username we could retrieve, username being needed we cannot ' - 'go further.', 'error') - logout() - return flask.redirect(return_url) + set_user(flask.g.fas_user) + return flask.redirect(return_url) - flask.session['_new_user'] = False - if not pagure.lib.search_user( - SESSION, username=flask.g.fas_user.username): - flask.session['_new_user'] = True +# Only import flask_oauthlib if it is needed +if APP.config.get('PAGURE_AUTH', None) == 'google': + from flask_oauthlib.client import OAuth + oauth = OAuth(APP) + + google = oauth.remote_app( + 'google', + consumer_key=APP.config.get('GOOGLE_ID'), + consumer_secret=APP.config.get('GOOGLE_SECRET'), + request_token_params={ + 'scope': 'email' + }, + base_url='https://www.googleapis.com/oauth2/v1/', + request_token_url=None, + access_token_method='POST', + access_token_url='https://accounts.google.com/o/oauth2/token', + authorize_url='https://accounts.google.com/o/oauth2/auth', + ) - try: - pagure.lib.set_up_user( - session=SESSION, - username=flask.g.fas_user.username, - fullname=flask.g.fas_user.fullname, - default_email=flask.g.fas_user.email, - ssh_key=flask.g.fas_user.get('ssh_key'), - keydir=APP.config.get('GITOLITE_KEYDIR', None), + @google.tokengetter + def get_google_oauth_token(): + return flask.session.get('google_token') + + @APP.before_request + def fas_user_from_google_user(): + if 'google_token' in flask.session: + u = google.get('userinfo').data + if 'error' in u: + flask.flash( + 'Error in Google authentication, please login again' + ) + APP.logger.error( + 'Failed Google auth: %s', u['error']['message'] + ) + else: + flask.g.fas_user = munch.Munch( + username=u['email'].split('@')[0], + fullname=u['name'], + email=u['email'], + ssh_key=None, + groups=[], + login_time=flask.session['google_logintime'], + ) + + @APP.route('/oauth-callback') + def oauth_callback(): + resp = google.authorized_response() + if resp is None: + return 'Access denied: reason=%s error=%s' % ( + request.args['error_reason'], + request.args['error_description'] ) + flask.session['google_token'] = (resp['access_token'], '') + flask.session['google_logintime'] = time.time() - # If groups are managed outside pagure, set up the user at login - if not APP.config.get('ENABLE_GROUP_MNGT', False): - user = pagure.lib.search_user( - SESSION, username=flask.g.fas_user.username) - groups = set(user.groups) - fas_groups = set(flask.g.fas_user.groups) - # Add the new groups - for group in fas_groups - groups: - groupobj = None - if group: - groupobj = pagure.lib.search_groups( - SESSION, group_name=group) - if groupobj: - try: - pagure.lib.add_user_to_group( - session=SESSION, - username=flask.g.fas_user.username, - group=groupobj, - user=flask.g.fas_user.username, - is_admin=is_admin(), - from_external=True, - ) - except pagure.exceptions.PagureException as err: - APP.logger.error(err) - # Remove the old groups - for group in groups - fas_groups: - if group: - try: - pagure.lib.delete_user_of_group( - session=SESSION, - username=flask.g.fas_user.username, - groupname=group, - user=flask.g.fas_user.username, - is_admin=is_admin(), - force=True, - from_external=True, - ) - except pagure.exceptions.PagureException as err: - APP.logger.error(err) - - SESSION.commit() - except SQLAlchemyError as err: - SESSION.rollback() - APP.logger.exception(err) - flask.flash( - 'Could not set up you as a user properly, please contact ' - 'an admin', 'error') - # Ensure the user is logged out if we cannot set them up - # correctly - logout() - return flask.redirect(return_url) + # we have just authenticated, so the before_request hook didn't + # apply to this request => run it manually + fas_user_from_google_user() + set_user(flask.g.fas_user) + + redirect_to = base64.b64decode(flask.request.args.get('state')) + return flask.redirect(redirect_to or flask.url_for('index')) SESSION = pagure.lib.create_session(APP.config['DB_URL']) @@ -225,6 +294,8 @@ def logout(): if auth in ['fas', 'openid']: if hasattr(flask.g, 'fas_user') and flask.g.fas_user is not None: FAS.logout() + elif auth == 'google': + flask.session.pop('google_token', None) elif auth == 'local': import pagure.ui.login as login login.logout() @@ -590,7 +661,8 @@ def auth_login(): # pragma: no cover else: # pragma: no cover admins = set([admins]) - if APP.config.get('PAGURE_AUTH', None) in ['fas', 'openid']: + auth_method = APP.config.get('PAGURE_AUTH', None) + if auth_method in ['fas', 'openid', 'google']: groups = set() if not APP.config.get('ENABLE_GROUP_MNGT', False): groups = [ @@ -601,8 +673,17 @@ def auth_login(): # pragma: no cover groups = set(groups).union(admins) ext_committer = set(APP.config.get('EXTERNAL_COMMITTER', {})) groups = set(groups).union(ext_committer) - return FAS.login(return_url=return_point, groups=groups) - elif APP.config.get('PAGURE_AUTH', None) == 'local': + if auth_method == 'google': + return google.authorize( + callback=flask.url_for( + 'oauth_callback', + _external=True, + ), + state=base64.b64encode(return_point) + ) + else: + return FAS.login(return_url=return_point, groups=groups) + elif auth_method == 'local': form = pagure.login_forms.LoginForm() return flask.render_template( 'login/login.html', diff --git a/pagure/default_config.py b/pagure/default_config.py index 0528217..401feba 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -11,7 +11,6 @@ import os from datetime import timedelta - # Set the time after which the admin session expires ADMIN_SESSION_LIFETIME = timedelta(minutes=20) @@ -205,10 +204,16 @@ FROM_EMAIL = 'pagure@pagure.org' DOMAIN_EMAIL_NOTIFICATIONS = 'pagure.org' SALT_EMAIL = '' -# Specify which authentication method to use, defaults to `fas` can be or -# `local` +# Specify which authentication method to use, defaults to `fas`, other +# possibilities are `local` or `google`. If `google` is used, you need +# to provide values for GOOGLE_ID and GOOGLE_SECRET (see below). # Default: ``fas``. -PAGURE_AUTH = 'fas' +PAGURE_AUTH = 'google' + +# You need to register a new project at +# https://console.developers.google.com/apis to get GOOGLE_* values +# GOOGLE_ID = 'MYID.apps.googleusercontent.com' +# GOOGLE_SECRET = 'MYSECRET' # When this is set to True, the session cookie will only be returned to the # server via ssl (https). If you connect to the server via plain http, the diff --git a/requirements.txt b/requirements.txt index 217112b..a2e54de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,6 +32,9 @@ wtforms # Needed only for local authentication and/or Pagure CI cryptography +# Required only for the `google` authentication backend +flask-oauthlib + # Required only for the `fas` and `openid` authentication backends python-fedora From b0523844ce09b095defe3b011607e0ab075aeb02 Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Dec 01 2017 10:05:09 +0000 Subject: [PATCH 2/4] Turn the Google OAuth authentication into general OAuth2 --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 5535d65..105535d 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -106,7 +106,7 @@ import pagure.proxy # noqa: E402 def set_user(user): if flask.g.fas_user.username is None: flask.flash( - 'It looks like your OpenID provider did not provide an ' + 'It looks like your identity provider did not provide an ' 'username we could retrieve, username being needed we cannot ' 'go further.', 'error') logout() @@ -190,63 +190,73 @@ if APP.config.get('PAGURE_AUTH', None) in ['fas', 'openid']: return flask.redirect(return_url) # Only import flask_oauthlib if it is needed -if APP.config.get('PAGURE_AUTH', None) == 'google': +if APP.config.get('PAGURE_AUTH', None) == 'oauth2': from flask_oauthlib.client import OAuth oauth = OAuth(APP) - google = oauth.remote_app( - 'google', - consumer_key=APP.config.get('GOOGLE_ID'), - consumer_secret=APP.config.get('GOOGLE_SECRET'), - request_token_params={ - 'scope': 'email' - }, - base_url='https://www.googleapis.com/oauth2/v1/', - request_token_url=None, - access_token_method='POST', - access_token_url='https://accounts.google.com/o/oauth2/token', - authorize_url='https://accounts.google.com/o/oauth2/auth', + oauth_provider = oauth.remote_app( + 'remote oauth app', + consumer_key=APP.config.get('OAUTH2_ID'), + consumer_secret=APP.config.get('OAUTH2_SECRET'), + request_token_params=APP.config.get('OAUTH2_REQUEST_TOKEN_PARAMS'), + base_url=APP.config.get('OAUTH2_BASE_URL'), + request_token_url=APP.config.get('OAUTH2_REQUEST_TOKEN_URL'), + access_token_method=APP.config.get('OAUTH2_ACCESS_TOKEN_METHOD'), + access_token_url=APP.config.get('OAUTH2_ACCESS_TOKEN_URL'), + authorize_url=APP.config.get('OAUTH2_AUTHORIZE_URL') ) - @google.tokengetter - def get_google_oauth_token(): - return flask.session.get('google_token') + @oauth_provider.tokengetter + def get_oauth_token(): + return flask.session.get('oauth_token') @APP.before_request - def fas_user_from_google_user(): - if 'google_token' in flask.session: - u = google.get('userinfo').data - if 'error' in u: + def fas_user_from_oauth_user(): + if 'oauth_token' in flask.session: + try: + u = oauth_provider.get( + APP.config.get('OAUTH2_USERNAME_INFO_PATH') + ) + username = APP.config.get( + 'OAUTH2_USERNAME_FROM_USERINFO' + )(u.data) + e = oauth_provider.get( + APP.config.get('OAUTH2_EMAIL_INFO_PATH') + ) + email = APP.config.get( + 'OAUTH2_EMAIL_FROM_USERINFO' + )(e.data) + except Exception as e: flask.flash( - 'Error in Google authentication, please login again' + 'Error in OAuth2 authentication, please login again' ) APP.logger.error( - 'Failed Google auth: %s', u['error']['message'] + 'Exception while getting username/email: %s', str(e) ) - else: + if True: flask.g.fas_user = munch.Munch( - username=u['email'].split('@')[0], - fullname=u['name'], - email=u['email'], + username=username, + fullname='', + email=email, ssh_key=None, groups=[], - login_time=flask.session['google_logintime'], + login_time=flask.session['oauth_logintime'], ) @APP.route('/oauth-callback') def oauth_callback(): - resp = google.authorized_response() - if resp is None: - return 'Access denied: reason=%s error=%s' % ( - request.args['error_reason'], - request.args['error_description'] - ) - flask.session['google_token'] = (resp['access_token'], '') - flask.session['google_logintime'] = time.time() + resp = oauth_provider.authorized_response() + if resp is None or 'access_token' not in resp: + # different providers return different error values, + # so we can only do very general error handling + APP.logger.info('Denied access to OAuth2 user: %s', vars(resp)) + return 'Access denied' + flask.session['oauth_token'] = (resp['access_token'], '') + flask.session['oauth_logintime'] = time.time() # we have just authenticated, so the before_request hook didn't # apply to this request => run it manually - fas_user_from_google_user() + fas_user_from_oauth_user() set_user(flask.g.fas_user) redirect_to = base64.b64decode(flask.request.args.get('state')) @@ -294,8 +304,8 @@ def logout(): if auth in ['fas', 'openid']: if hasattr(flask.g, 'fas_user') and flask.g.fas_user is not None: FAS.logout() - elif auth == 'google': - flask.session.pop('google_token', None) + elif auth == 'oauth2': + flask.session.pop('oauth_token', None) elif auth == 'local': import pagure.ui.login as login login.logout() @@ -662,7 +672,7 @@ def auth_login(): # pragma: no cover admins = set([admins]) auth_method = APP.config.get('PAGURE_AUTH', None) - if auth_method in ['fas', 'openid', 'google']: + if auth_method in ['fas', 'openid', 'oauth2']: groups = set() if not APP.config.get('ENABLE_GROUP_MNGT', False): groups = [ @@ -673,8 +683,8 @@ def auth_login(): # pragma: no cover groups = set(groups).union(admins) ext_committer = set(APP.config.get('EXTERNAL_COMMITTER', {})) groups = set(groups).union(ext_committer) - if auth_method == 'google': - return google.authorize( + if auth_method == 'oauth2': + return oauth_provider.authorize( callback=flask.url_for( 'oauth_callback', _external=True, diff --git a/pagure/default_config.py b/pagure/default_config.py index 401feba..1c94dde 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -205,15 +205,44 @@ DOMAIN_EMAIL_NOTIFICATIONS = 'pagure.org' SALT_EMAIL = '' # Specify which authentication method to use, defaults to `fas`, other -# possibilities are `local` or `google`. If `google` is used, you need -# to provide values for GOOGLE_ID and GOOGLE_SECRET (see below). +# possibilities are `local` or `oauth2`. If `oauth2` is used, you need +# to provide values for OAUTH2_* values (see below). # Default: ``fas``. -PAGURE_AUTH = 'google' - -# You need to register a new project at -# https://console.developers.google.com/apis to get GOOGLE_* values -# GOOGLE_ID = 'MYID.apps.googleusercontent.com' -# GOOGLE_SECRET = 'MYSECRET' +PAGURE_AUTH = 'fas' + +# As an example of OAuth2 authentication values, here is how you could +# setup Google authentication. You need to register a new project at +# https://console.developers.google.com/apis to get OAUTH2_* values +# Also, you can see some examples from flask-oauthlib at +# https://github.com/lepture/flask-oauthlib/tree/master/example +# OAUTH2_ID = 'GoogleAppID.apps.googleusercontent.com' +# OAUTH2_SECRET = 'GoogleAppSecret' +# Note that scope names vary across oauth providers +# OAUTH2_REQUEST_TOKEN_PARAMS = {'scope': 'openid email'} +# OAUTH2_BASE_URL = 'https://www.googleapis.com/oauth2/v1/' +# OAUTH2_REQUEST_TOKEN_URL = None +# OAUTH2_ACCESS_TOKEN_METHOD = 'POST' +# OAUTH2_ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' +# OAUTH2_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth' +# OAUTH2_USERNAME_INFO_PATH = 'userinfo' +# OAUTH2_USERNAME_FROM_USERINFO = lambda x: x['email'].split('@')[0] +# OAUTH2_EMAIL_INFO_PATH = OAUTH2_USERNAME_INFO_PATH +# OAUTH2_EMAIL_FROM_USERINFO = lambda x: x['email'] + +# Example for setting up Github OAuth2 authentication: +# OAUTH2_ID = 'GithubAppID' +# OAUTH2_SECRET = 'GithuAppSecret' +# OAUTH2_REQUEST_TOKEN_PARAMS = {'scope': 'user'} +# OAUTH2_BASE_URL = 'https://api.github.com/' +# OAUTH2_REQUEST_TOKEN_URL = None +# OAUTH2_ACCESS_TOKEN_METHOD = 'POST' +# OAUTH2_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' +# OAUTH2_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize' +# OAUTH2_USERNAME_INFO_PATH = 'user' +# OAUTH2_USERNAME_FROM_USERINFO = lambda x: x['login'] +# OAUTH2_EMAIL_INFO_PATH = 'user/emails' +# OAUTH2_EMAIL_FROM_USERINFO = \ +# lambda x: filter(lambda i: i['primary'], x)[0]['email'] # When this is set to True, the session cookie will only be returned to the # server via ssl (https). If you connect to the server via plain http, the From a0757915cf4dead99e9fdf0189b19d9b523d21f6 Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Dec 01 2017 10:09:43 +0000 Subject: [PATCH 3/4] Verify oauth callback return url --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 105535d..8059cb6 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -260,6 +260,8 @@ if APP.config.get('PAGURE_AUTH', None) == 'oauth2': set_user(flask.g.fas_user) redirect_to = base64.b64decode(flask.request.args.get('state')) + if not is_safe_url(redirect_to): + redirect_to = None return flask.redirect(redirect_to or flask.url_for('index')) From eddec0d16051cb42ad9a491316a36f4e5eaeb0b9 Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Dec 01 2017 13:38:45 +0000 Subject: [PATCH 4/4] Use plugin system for different oauth IdPs --- diff --git a/pagure/__init__.py b/pagure/__init__.py index 8059cb6..ce1be99 100644 --- a/pagure/__init__.py +++ b/pagure/__init__.py @@ -32,6 +32,7 @@ import pygit2 # noqa: E402 import werkzeug # noqa: E402 from functools import wraps # noqa: E402 from sqlalchemy.exc import SQLAlchemyError # noqa: E402 +from straight.plugin import load # noqa: E402 from flask_multistatic import MultiStaticFlask # noqa: E402 @@ -192,8 +193,20 @@ if APP.config.get('PAGURE_AUTH', None) in ['fas', 'openid']: # Only import flask_oauthlib if it is needed if APP.config.get('PAGURE_AUTH', None) == 'oauth2': from flask_oauthlib.client import OAuth + from pagure.oauth_plugins.base import BaseOAuthPlugin oauth = OAuth(APP) + # load the required oauth provider plugin + all_plugins = load( + APP.config.get('OAUTH_PLUGIN_MODULE', 'pagure.oauth_plugins'), + subclasses=BaseOAuthPlugin + ) + oauth_plugin = next(iter(filter( + lambda p: p.name == APP.config.get('OAUTH_PROVIDER'), + all_plugins.produce() + ))) + oauth_plugin.configure_app(APP) + oauth_provider = oauth.remote_app( 'remote oauth app', consumer_key=APP.config.get('OAUTH2_ID'), @@ -214,18 +227,14 @@ if APP.config.get('PAGURE_AUTH', None) == 'oauth2': def fas_user_from_oauth_user(): if 'oauth_token' in flask.session: try: - u = oauth_provider.get( - APP.config.get('OAUTH2_USERNAME_INFO_PATH') - ) - username = APP.config.get( - 'OAUTH2_USERNAME_FROM_USERINFO' - )(u.data) - e = oauth_provider.get( - APP.config.get('OAUTH2_EMAIL_INFO_PATH') + flask.g.fas_user = munch.Munch( + username=oauth_plugin.get_username(oauth_provider), + fullname=oauth_plugin.get_fullname(oauth_provider), + email=oauth_plugin.get_email(oauth_provider), + ssh_key=oauth_plugin.get_ssh_key(oauth_provider), + groups=oauth_plugin.get_groups(oauth_provider), + login_time=flask.session['oauth_logintime'], ) - email = APP.config.get( - 'OAUTH2_EMAIL_FROM_USERINFO' - )(e.data) except Exception as e: flask.flash( 'Error in OAuth2 authentication, please login again' @@ -233,15 +242,6 @@ if APP.config.get('PAGURE_AUTH', None) == 'oauth2': APP.logger.error( 'Exception while getting username/email: %s', str(e) ) - if True: - flask.g.fas_user = munch.Munch( - username=username, - fullname='', - email=email, - ssh_key=None, - groups=[], - login_time=flask.session['oauth_logintime'], - ) @APP.route('/oauth-callback') def oauth_callback(): diff --git a/pagure/default_config.py b/pagure/default_config.py index 1c94dde..747f460 100644 --- a/pagure/default_config.py +++ b/pagure/default_config.py @@ -206,43 +206,13 @@ SALT_EMAIL = '' # Specify which authentication method to use, defaults to `fas`, other # possibilities are `local` or `oauth2`. If `oauth2` is used, you need -# to provide values for OAUTH2_* values (see below). +# to provide values for OAUTH2_* values below. # Default: ``fas``. PAGURE_AUTH = 'fas' - -# As an example of OAuth2 authentication values, here is how you could -# setup Google authentication. You need to register a new project at -# https://console.developers.google.com/apis to get OAUTH2_* values -# Also, you can see some examples from flask-oauthlib at -# https://github.com/lepture/flask-oauthlib/tree/master/example -# OAUTH2_ID = 'GoogleAppID.apps.googleusercontent.com' -# OAUTH2_SECRET = 'GoogleAppSecret' -# Note that scope names vary across oauth providers -# OAUTH2_REQUEST_TOKEN_PARAMS = {'scope': 'openid email'} -# OAUTH2_BASE_URL = 'https://www.googleapis.com/oauth2/v1/' -# OAUTH2_REQUEST_TOKEN_URL = None -# OAUTH2_ACCESS_TOKEN_METHOD = 'POST' -# OAUTH2_ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' -# OAUTH2_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth' -# OAUTH2_USERNAME_INFO_PATH = 'userinfo' -# OAUTH2_USERNAME_FROM_USERINFO = lambda x: x['email'].split('@')[0] -# OAUTH2_EMAIL_INFO_PATH = OAUTH2_USERNAME_INFO_PATH -# OAUTH2_EMAIL_FROM_USERINFO = lambda x: x['email'] - -# Example for setting up Github OAuth2 authentication: -# OAUTH2_ID = 'GithubAppID' -# OAUTH2_SECRET = 'GithuAppSecret' -# OAUTH2_REQUEST_TOKEN_PARAMS = {'scope': 'user'} -# OAUTH2_BASE_URL = 'https://api.github.com/' -# OAUTH2_REQUEST_TOKEN_URL = None -# OAUTH2_ACCESS_TOKEN_METHOD = 'POST' -# OAUTH2_ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' -# OAUTH2_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize' -# OAUTH2_USERNAME_INFO_PATH = 'user' -# OAUTH2_USERNAME_FROM_USERINFO = lambda x: x['login'] -# OAUTH2_EMAIL_INFO_PATH = 'user/emails' -# OAUTH2_EMAIL_FROM_USERINFO = \ -# lambda x: filter(lambda i: i['primary'], x)[0]['email'] +# See ``pagure/oauth_plugins/`` for list of available plugins +# OAUTH_PROVIDER = 'google' +# OAUTH2_ID = 'id' +# OAUTH2_SECRET = 'secret' # When this is set to True, the session cookie will only be returned to the # server via ssl (https). If you connect to the server via plain http, the diff --git a/pagure/oauth_plugins/__init__.py b/pagure/oauth_plugins/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pagure/oauth_plugins/__init__.py diff --git a/pagure/oauth_plugins/base.py b/pagure/oauth_plugins/base.py new file mode 100644 index 0000000..71275c2 --- /dev/null +++ b/pagure/oauth_plugins/base.py @@ -0,0 +1,24 @@ +class BaseOAuthPlugin(object): + @classmethod + def configure_app(cls, app): + raise NotImplementedError() + + @classmethod + def get_username(cls, provider): + raise NotImplementedError() + + @classmethod + def get_fullname(cls, provider): + raise NotImplementedError() + + @classmethod + def get_email(cls, provider): + raise NotImplementedError() + + @classmethod + def get_ssh_key(cls, provider): + raise NotImplementedError() + + @classmethod + def get_groups(cls, provider): + raise NotImplementedError() diff --git a/pagure/oauth_plugins/github.py b/pagure/oauth_plugins/github.py new file mode 100644 index 0000000..94ca97b --- /dev/null +++ b/pagure/oauth_plugins/github.py @@ -0,0 +1,37 @@ +from .base import BaseOAuthPlugin + +class GithubOAuth(BaseOAuthPlugin): + name = 'github' + + @classmethod + def configure_app(cls, app): + app.config.update({ + 'OAUTH2_REQUEST_TOKEN_PARAMS': {'scope': 'user'}, + 'OAUTH2_BASE_URL': 'https://api.github.com/', + 'OAUTH2_REQUEST_TOKEN_URL': None, + 'OAUTH2_ACCESS_TOKEN_METHOD': 'POST', + 'OAUTH2_ACCESS_TOKEN_URL': + 'https://github.com/login/oauth/access_token', + 'OAUTH2_AUTHORIZE_URL': 'https://github.com/login/oauth/authorize', + }) + + @classmethod + def get_username(cls, provider): + return provider.get('user').data['login'] + + @classmethod + def get_fullname(cls, provider): + return provider.get('user').data['name'] + + @classmethod + def get_email(cls, provider): + emails = provider.get('user/emails').data + return next(iter(filter(lambda i: i['primary'], emails)))['email'] + + @classmethod + def get_ssh_key(cls, provider): + return None + + @classmethod + def get_groups(cls, provider): + return [] diff --git a/pagure/oauth_plugins/google.py b/pagure/oauth_plugins/google.py new file mode 100644 index 0000000..d27f902 --- /dev/null +++ b/pagure/oauth_plugins/google.py @@ -0,0 +1,39 @@ +from .base import BaseOAuthPlugin + +class GoogleOAuth(BaseOAuthPlugin): + name = 'google' + + @classmethod + def configure_app(cls, app): + app.config.update({ + 'OAUTH2_REQUEST_TOKEN_PARAMS': {'scope': 'openid email'}, + 'OAUTH2_BASE_URL': 'https://www.googleapis.com/oauth2/v1/', + 'OAUTH2_REQUEST_TOKEN_URL': None, + 'OAUTH2_ACCESS_TOKEN_METHOD': 'POST', + 'OAUTH2_ACCESS_TOKEN_URL': + 'https://accounts.google.com/o/oauth2/token', + 'OAUTH2_AUTHORIZE_URL': 'https://accounts.google.com/o/oauth2/auth', + }) + + @classmethod + def get_username(cls, provider): + return cls.get_email(provider).split('@')[0] + + @classmethod + def get_fullname(cls, provider): + return '{given} {family}'.format( + given=provider.get('userinfo').data['given_name'], + family=provider.get('userinfo').data['family_name'] + ) + + @classmethod + def get_email(cls, provider): + return provider.get('userinfo').data['email'] + + @classmethod + def get_ssh_key(cls, provider): + return None + + @classmethod + def get_groups(cls, provider): + return []