From 6c2495ce15c6bd82cd3eff285932c3fdbfef73a4 Mon Sep 17 00:00:00 2001
From: Slavek Kabrda <bkabrda@redhat.com>
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 = '<secret key to be changed>'
 
-# 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 <bkabrda@redhat.com>
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 = '<secret key to be changed>'
 
 # 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 <bkabrda@redhat.com>
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 <bkabrda@redhat.com>
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 = '<secret key to be changed>'
 
 # 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 []

