- Make the OAuth config system centralized

- Add support for Github Enterprise login
This commit is contained in:
Joseph Schorr 2014-11-05 16:43:37 -05:00
parent 6deafe8c86
commit 3e79379942
11 changed files with 196 additions and 83 deletions

6
app.py
View file

@ -19,6 +19,7 @@ from util.analytics import Analytics
from util.exceptionlog import Sentry from util.exceptionlog import Sentry
from util.queuemetrics import QueueMetrics from util.queuemetrics import QueueMetrics
from util.names import urn_generator from util.names import urn_generator
from util.oauth import GoogleOAuthConfig, GithubOAuthConfig
from data.billing import Billing from data.billing import Billing
from data.buildlogs import BuildLogs from data.buildlogs import BuildLogs
from data.archivedlogs import LogArchive from data.archivedlogs import LogArchive
@ -131,6 +132,11 @@ queue_metrics = QueueMetrics(app)
authentication = UserAuthentication(app) authentication = UserAuthentication(app)
userevents = UserEventsBuilderModule(app) userevents = UserEventsBuilderModule(app)
github_login = GithubOAuthConfig(app, 'GITHUB_LOGIN_CONFIG')
github_trigger = GithubOAuthConfig(app, 'GITHUB_TRIGGER_CONFIG')
google_login = GoogleOAuthConfig(app, 'GOOGLE_LOGIN_CONFIG')
oauth_apps = [github_login, github_trigger, google_login]
tf = app.config['DB_TRANSACTION_FACTORY'] tf = app.config['DB_TRANSACTION_FACTORY']
image_diff_queue = WorkQueue(app.config['DIFFS_QUEUE_NAME'], tf) image_diff_queue = WorkQueue(app.config['DIFFS_QUEUE_NAME'], tf)
dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf, dockerfile_build_queue = WorkQueue(app.config['DOCKERFILE_BUILD_QUEUE_NAME'], tf,

View file

@ -15,11 +15,10 @@ def build_requests_session():
# The set of configuration key names that will be accessible in the client. Since these # The set of configuration key names that will be accessible in the client. Since these
# values are set to the frontend, DO NOT PLACE ANY SECRETS OR KEYS in this list. # values are sent to the frontend, DO NOT PLACE ANY SECRETS OR KEYS in this list.
CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'GITHUB_CLIENT_ID', CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'MIXPANEL_KEY',
'GITHUB_LOGIN_CLIENT_ID', 'MIXPANEL_KEY', 'STRIPE_PUBLISHABLE_KEY', 'STRIPE_PUBLISHABLE_KEY', 'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN',
'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN', 'AUTHENTICATION_TYPE', 'AUTHENTICATION_TYPE', 'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT',
'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT', 'GOOGLE_LOGIN_CLIENT_ID',
'CONTACT_INFO'] 'CONTACT_INFO']
@ -108,22 +107,11 @@ class DefaultConfig(object):
SENTRY_PUBLIC_DSN = None SENTRY_PUBLIC_DSN = None
# Github Config # Github Config
GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token' GITHUB_LOGIN_CONFIG = None
GITHUB_USER_URL = 'https://api.github.com/user' GITHUB_TRIGGER_CONFIG = None
GITHUB_USER_EMAILS = GITHUB_USER_URL + '/emails'
GITHUB_CLIENT_ID = ''
GITHUB_CLIENT_SECRET = ''
GITHUB_LOGIN_CLIENT_ID = ''
GITHUB_LOGIN_CLIENT_SECRET = ''
# Google Config. # Google Config.
GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' GOOGLE_LOGIN_CONFIG = None
GOOGLE_USER_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'
GOOGLE_LOGIN_CLIENT_ID = ''
GOOGLE_LOGIN_CLIENT_SECRET = ''
# Requests based HTTP client with a large request pool # Requests based HTTP client with a large request pool
HTTPCLIENT = build_requests_session() HTTPCLIENT = build_requests_session()

View file

@ -1,10 +1,11 @@
import logging import logging
import requests
from flask import request, redirect, url_for, Blueprint from flask import request, redirect, url_for, Blueprint
from flask.ext.login import current_user from flask.ext.login import current_user
from endpoints.common import render_page_template, common_login, route_show_if from endpoints.common import render_page_template, common_login, route_show_if
from app import app, analytics, get_app_url from app import app, analytics, get_app_url, github_login, google_login, github_trigger
from data import model from data import model
from util.names import parse_repository_name from util.names import parse_repository_name
from util.validation import generate_valid_usernames from util.validation import generate_valid_usernames
@ -29,20 +30,16 @@ def render_ologin_error(service_name,
service_url=get_app_url(), service_url=get_app_url(),
user_creation=features.USER_CREATION) user_creation=features.USER_CREATION)
def exchange_code_for_token(code, service_name='GITHUB', for_login=True, form_encode=False, def exchange_code_for_token(code, service, form_encode=False, redirect_suffix=''):
redirect_suffix=''):
code = request.args.get('code') code = request.args.get('code')
id_config = service_name + '_LOGIN_CLIENT_ID' if for_login else service_name + '_CLIENT_ID'
secret_config = service_name + '_LOGIN_CLIENT_SECRET' if for_login else service_name + '_CLIENT_SECRET'
payload = { payload = {
'client_id': app.config[id_config], 'client_id': service.client_id(),
'client_secret': app.config[secret_config], 'client_secret': service.client_secret(),
'code': code, 'code': code,
'grant_type': 'authorization_code', 'grant_type': 'authorization_code',
'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app.config['PREFERRED_URL_SCHEME'], 'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app.config['PREFERRED_URL_SCHEME'],
app.config['SERVER_HOSTNAME'], app.config['SERVER_HOSTNAME'],
service_name.lower(), service.service_name().lower(),
redirect_suffix) redirect_suffix)
} }
@ -50,12 +47,11 @@ def exchange_code_for_token(code, service_name='GITHUB', for_login=True, form_en
'Accept': 'application/json' 'Accept': 'application/json'
} }
token_url = service.token_endpoint()
if form_encode: if form_encode:
get_access_token = client.post(app.config[service_name + '_TOKEN_URL'], get_access_token = client.post(token_url, data=payload, headers=headers)
data=payload, headers=headers)
else: else:
get_access_token = client.post(app.config[service_name + '_TOKEN_URL'], get_access_token = client.post(token_url, params=payload, headers=headers)
params=payload, headers=headers)
json_data = get_access_token.json() json_data = get_access_token.json()
if not json_data: if not json_data:
@ -65,25 +61,20 @@ def exchange_code_for_token(code, service_name='GITHUB', for_login=True, form_en
return token return token
def get_github_user(token): def get_user(service, token):
token_param = {
'access_token': token,
}
get_user = client.get(app.config['GITHUB_USER_URL'], params=token_param)
return get_user.json()
def get_google_user(token):
token_param = { token_param = {
'access_token': token, 'access_token': token,
'alt': 'json', 'alt': 'json',
} }
get_user = client.get(service.user_endpoint(), params=token_param)
if get_user.status_code != requests.codes.ok:
return {}
get_user = client.get(app.config['GOOGLE_USER_URL'], params=token_param)
return get_user.json() return get_user.json()
def conduct_oauth_login(service_name, user_id, username, email, metadata={}):
def conduct_oauth_login(service, user_id, username, email, metadata={}):
service_name = service.service_name()
to_login = model.verify_federated_login(service_name.lower(), user_id) to_login = model.verify_federated_login(service_name.lower(), user_id)
if not to_login: if not to_login:
# See if we can create a new user. # See if we can create a new user.
@ -138,8 +129,8 @@ def google_oauth_callback():
if error: if error:
return render_ologin_error('Google', error) return render_ologin_error('Google', error)
token = exchange_code_for_token(request.args.get('code'), service_name='GOOGLE', form_encode=True) token = exchange_code_for_token(request.args.get('code'), google_login, form_encode=True)
user_data = get_google_user(token) user_data = get_user(google_login, token)
if not user_data or not user_data.get('id', None) or not user_data.get('email', None): if not user_data or not user_data.get('id', None) or not user_data.get('email', None):
return render_ologin_error('Google') return render_ologin_error('Google')
@ -148,7 +139,7 @@ def google_oauth_callback():
'service_username': user_data['email'] 'service_username': user_data['email']
} }
return conduct_oauth_login('Google', user_data['id'], username, user_data['email'], return conduct_oauth_login(google_login, user_data['id'], username, user_data['email'],
metadata=metadata) metadata=metadata)
@ -159,8 +150,8 @@ def github_oauth_callback():
if error: if error:
return render_ologin_error('GitHub', error) return render_ologin_error('GitHub', error)
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB') token = exchange_code_for_token(request.args.get('code'), github_login)
user_data = get_github_user(token) user_data = get_user(github_login, token)
if not user_data or not 'login' in user_data: if not user_data or not 'login' in user_data:
return render_ologin_error('GitHub') return render_ologin_error('GitHub')
@ -174,7 +165,7 @@ def github_oauth_callback():
token_param = { token_param = {
'access_token': token, 'access_token': token,
} }
get_email = client.get(app.config['GITHUB_USER_EMAILS'], params=token_param, get_email = client.get(github_login.email_endpoint(), params=token_param,
headers=v3_media_type) headers=v3_media_type)
# We will accept any email, but we prefer the primary # We will accept any email, but we prefer the primary
@ -188,17 +179,17 @@ def github_oauth_callback():
'service_username': username 'service_username': username
} }
return conduct_oauth_login('github', github_id, username, found_email, metadata=metadata) return conduct_oauth_login(github_login, github_id, username, found_email, metadata=metadata)
@callback.route('/google/callback/attach', methods=['GET']) @callback.route('/google/callback/attach', methods=['GET'])
@route_show_if(features.GOOGLE_LOGIN) @route_show_if(features.GOOGLE_LOGIN)
@require_session_login @require_session_login
def google_oauth_attach(): def google_oauth_attach():
token = exchange_code_for_token(request.args.get('code'), service_name='GOOGLE', token = exchange_code_for_token(request.args.get('code'), google_login,
redirect_suffix='/attach', form_encode=True) redirect_suffix='/attach', form_encode=True)
user_data = get_google_user(token) user_data = get_user(google_login, token)
if not user_data or not user_data.get('id', None): if not user_data or not user_data.get('id', None):
return render_ologin_error('Google') return render_ologin_error('Google')
@ -224,8 +215,8 @@ def google_oauth_attach():
@route_show_if(features.GITHUB_LOGIN) @route_show_if(features.GITHUB_LOGIN)
@require_session_login @require_session_login
def github_oauth_attach(): def github_oauth_attach():
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB') token = exchange_code_for_token(request.args.get('code'), github_login)
user_data = get_github_user(token) user_data = get_user(github_login, token)
if not user_data: if not user_data:
return render_ologin_error('GitHub') return render_ologin_error('GitHub')
@ -255,8 +246,7 @@ def github_oauth_attach():
def attach_github_build_trigger(namespace, repository): def attach_github_build_trigger(namespace, repository):
permission = AdministerRepositoryPermission(namespace, repository) permission = AdministerRepositoryPermission(namespace, repository)
if permission.can(): if permission.can():
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB', token = exchange_code_for_token(request.args.get('code'), github_trigger)
for_login=False)
repo = model.get_repository(namespace, repository) repo = model.get_repository(namespace, repository)
if not repo: if not repo:
msg = 'Invalid repository: %s/%s' % (namespace, repository) msg = 'Invalid repository: %s/%s' % (namespace, repository)

View file

@ -11,7 +11,8 @@ from random import SystemRandom
from data import model from data import model
from data.database import db from data.database import db
from app import app, login_manager, dockerfile_build_queue, notification_queue from app import app, login_manager, dockerfile_build_queue, notification_queue, oauth_apps
from auth.permissions import QuayDeferredPermissionUser from auth.permissions import QuayDeferredPermissionUser
from auth import scopes from auth import scopes
from endpoints.api.discovery import swagger_route_data from endpoints.api.discovery import swagger_route_data
@ -176,6 +177,16 @@ def render_page_template(name, **kwargs):
external_styles = get_external_css(local=not app.config.get('USE_CDN', True)) external_styles = get_external_css(local=not app.config.get('USE_CDN', True))
external_scripts = get_external_javascript(local=not app.config.get('USE_CDN', True)) external_scripts = get_external_javascript(local=not app.config.get('USE_CDN', True))
def get_oauth_config():
oauth_config = {}
for oauth_app in oauth_apps:
oauth_config[oauth_app.key_name] = {
'CLIENT_ID': oauth_app.client_id(),
'AUTHORIZE_ENDPOINT': oauth_app.authorize_endpoint()
}
return oauth_config
contact_href = None contact_href = None
if len(app.config.get('CONTACT_INFO', [])) == 1: if len(app.config.get('CONTACT_INFO', [])) == 1:
contact_href = app.config['CONTACT_INFO'][0] contact_href = app.config['CONTACT_INFO'][0]
@ -189,6 +200,7 @@ def render_page_template(name, **kwargs):
library_scripts=library_scripts, library_scripts=library_scripts,
feature_set=json.dumps(features.get_features()), feature_set=json.dumps(features.get_features()),
config_set=json.dumps(getFrontendVisibleConfig(app.config)), config_set=json.dumps(getFrontendVisibleConfig(app.config)),
oauth_set=json.dumps(get_oauth_config()),
mixpanel_key=app.config.get('MIXPANEL_KEY', ''), mixpanel_key=app.config.get('MIXPANEL_KEY', ''),
google_analytics_key=app.config.get('GOOGLE_ANALYTICS_KEY', ''), google_analytics_key=app.config.get('GOOGLE_ANALYTICS_KEY', ''),
sentry_public_dsn=app.config.get('SENTRY_PUBLIC_DSN', ''), sentry_public_dsn=app.config.get('SENTRY_PUBLIC_DSN', ''),

View file

@ -8,7 +8,7 @@ import re
from github import Github, UnknownObjectException, GithubException from github import Github, UnknownObjectException, GithubException
from tempfile import SpooledTemporaryFile from tempfile import SpooledTemporaryFile
from app import app, userfiles as user_files from app import app, userfiles as user_files, github_trigger
from util.tarfileappender import TarfileAppender from util.tarfileappender import TarfileAppender
@ -150,8 +150,8 @@ def raise_unsupported():
class GithubBuildTrigger(BuildTrigger): class GithubBuildTrigger(BuildTrigger):
@staticmethod @staticmethod
def _get_client(auth_token): def _get_client(auth_token):
return Github(auth_token, client_id=app.config['GITHUB_CLIENT_ID'], return Github(auth_token, client_id=github_trigger.client_id(),
client_secret=app.config['GITHUB_CLIENT_SECRET']) client_secret=github_trigger.client_secret())
@classmethod @classmethod
def service_name(cls): def service_name(cls):

View file

@ -2,8 +2,15 @@
<span ng-if="provider == 'github'"> <span ng-if="provider == 'github'">
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']" ng-click="startSignin('github')" style="margin-bottom: 10px" ng-disabled="signingIn"> <a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']" ng-click="startSignin('github')" style="margin-bottom: 10px" ng-disabled="signingIn">
<i class="fa fa-github fa-lg"></i> <i class="fa fa-github fa-lg"></i>
<span ng-if="action != 'attach'">Sign In with GitHub</span> <span ng-if="action != 'attach'">
<span ng-if="action == 'attach'">Attach to GitHub Account</span> Sign In with GitHub
<span ng-if="isEnterprise('github')">Enterprise</span>
</span>
<span ng-if="action == 'attach'">
Attach to GitHub
<span ng-if="isEnterprise('github')">Enterprise</span>
Account
</span>
</a> </a>
</span> </span>

View file

@ -620,7 +620,8 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
}]); }]);
$provide.factory('TriggerService', ['UtilService', '$sanitize', function(UtilService, $sanitize) { $provide.factory('TriggerService', ['UtilService', '$sanitize', 'KeyService',
function(UtilService, $sanitize, KeyService) {
var triggerService = {}; var triggerService = {};
var triggerTypes = { var triggerTypes = {
@ -639,9 +640,28 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
'type': 'option', 'type': 'option',
'name': 'branch_name' 'name': 'branch_name'
} }
] ],
'get_redirect_url': function(namespace, repository) {
var redirect_uri = KeyService['githubRedirectUri'] + '/trigger/' +
namespace + '/' + repository;
var authorize_url = KeyService['githubTriggerAuthorizeUrl'];
var client_id = KeyService['githubTriggerClientId'];
return authorize_url + 'client_id=' + client_id +
'&scope=repo,user:email&redirect_uri=' + redirect_uri;
} }
} }
}
triggerService.getRedirectUrl = function(name, namespace, repository) {
var type = triggerTypes[name];
if (!type) {
return '';
}
return type['get_redirect_url'](namespace, repository);
};
triggerService.getDescription = function(name, config) { triggerService.getDescription = function(name, config) {
var type = triggerTypes[name]; var type = triggerTypes[name];
@ -1693,21 +1713,29 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
$provide.factory('KeyService', ['$location', 'Config', function($location, Config) { $provide.factory('KeyService', ['$location', 'Config', function($location, Config) {
var keyService = {} var keyService = {}
var oauth = window.__oauth;
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY']; keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID']; keyService['githubTriggerClientId'] = oauth['GITHUB_TRIGGER_CONFIG']['CLIENT_ID'];
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID']; keyService['githubLoginClientId'] = oauth['GITHUB_LOGIN_CONFIG']['CLIENT_ID'];
keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback'); keyService['googleLoginClientId'] = oauth['GOOGLE_LOGIN_CONFIG']['CLIENT_ID'];
keyService['googleLoginClientId'] = Config['GOOGLE_LOGIN_CLIENT_ID']; keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback');
keyService['googleRedirectUri'] = Config.getUrl('/oauth2/google/callback'); keyService['googleRedirectUri'] = Config.getUrl('/oauth2/google/callback');
keyService['googleLoginUrl'] = 'https://accounts.google.com/o/oauth2/auth?response_type=code&'; keyService['githubLoginUrl'] = oauth['GITHUB_LOGIN_CONFIG']['AUTHORIZE_ENDPOINT'];
keyService['githubLoginUrl'] = Config['GITHUB_LOGIN_URL'] + '?'; keyService['googleLoginUrl'] = oauth['GOOGLE_LOGIN_CONFIG']['AUTHORIZE_ENDPOINT'];
keyService['githubTriggerAuthorizeUrl'] = oauth['GITHUB_LOGIN_CONFIG']['AUTHORIZE_ENDPOINT'];
keyService['googleLoginScope'] = 'openid email';
keyService['githubLoginScope'] = 'user:email'; keyService['githubLoginScope'] = 'user:email';
keyService['googleLoginScope'] = 'openid email';
keyService.isEnterprise = function(service) {
var isGithubEnterprise = keyService['githubLoginUrl'].indexOf('https://github.com/') < 0;
return service == 'github' && isGithubEnterprise;
};
keyService.getExternalLoginUrl = function(service, action) { keyService.getExternalLoginUrl = function(service, action) {
var state_clause = ''; var state_clause = '';
@ -2688,6 +2716,8 @@ quayApp.directive('externalLoginButton', function () {
}, },
controller: function($scope, $timeout, $interval, ApiService, KeyService, CookieService, Features, Config) { controller: function($scope, $timeout, $interval, ApiService, KeyService, CookieService, Features, Config) {
$scope.signingIn = false; $scope.signingIn = false;
$scope.isEnterprise = KeyService.isEnterprise;
$scope.startSignin = function(service) { $scope.startSignin = function(service) {
$scope.signInStarted({'service': service}); $scope.signInStarted({'service': service});

View file

@ -1330,15 +1330,13 @@ function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, TriggerServi
var name = $routeParams.name; var name = $routeParams.name;
$scope.Features = Features; $scope.Features = Features;
$scope.TriggerService = TriggerService;
$scope.permissions = {'team': [], 'user': [], 'loading': 2}; $scope.permissions = {'team': [], 'user': [], 'loading': 2};
$scope.logsShown = 0; $scope.logsShown = 0;
$scope.deleting = false; $scope.deleting = false;
$scope.permissionCache = {}; $scope.permissionCache = {};
$scope.githubRedirectUri = KeyService.githubRedirectUri;
$scope.githubClientId = KeyService.githubClientId;
$scope.showTriggerSetupCounter = 0; $scope.showTriggerSetupCounter = 0;
$scope.getBadgeFormat = function(format, repo) { $scope.getBadgeFormat = function(format, repo) {
@ -2030,12 +2028,10 @@ function V1Ctrl($scope, $location, UserService) {
UserService.updateUserIn($scope); UserService.updateUserIn($scope);
} }
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService, KeyService, Features) { function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService, TriggerService, Features) {
UserService.updateUserIn($scope); UserService.updateUserIn($scope);
$scope.Features = Features; $scope.Features = Features;
$scope.githubRedirectUri = KeyService.githubRedirectUri;
$scope.githubClientId = KeyService.githubClientId;
$scope.repo = { $scope.repo = {
'is_public': 0, 'is_public': 0,
@ -2114,9 +2110,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService
// Conduct the Github redirect if applicable. // Conduct the Github redirect if applicable.
if ($scope.repo.initialize == 'github') { if ($scope.repo.initialize == 'github') {
window.location = 'https://github.com/login/oauth/authorize?client_id=' + $scope.githubClientId + window.location = TriggerService.getRedirectUrl('github', repo.namespace, repo.name);
'&scope=repo,user:email&redirect_uri=' + $scope.githubRedirectUri + '/trigger/' +
repo.namespace + '/' + repo.name;
return; return;
} }

View file

@ -306,7 +306,11 @@
<b class="caret"></b> <b class="caret"></b>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right pull-right"> <ul class="dropdown-menu dropdown-menu-right pull-right">
<li><a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=repo,user:email&redirect_uri={{ githubRedirectUri }}/trigger/{{ repo.namespace }}/{{ repo.name }}"><i class="fa fa-github fa-lg"></i>GitHub - Repository Push</a></li> <li>
<a href="{{ TriggerService.getRedirectUrl('github', repo.namespace, repo.name) }}">
<i class="fa fa-github fa-lg"></i>GitHub - Repository Push
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -44,6 +44,7 @@
window.__endpoints = {{ route_data|safe }}.apis; window.__endpoints = {{ route_data|safe }}.apis;
window.__features = {{ feature_set|safe }}; window.__features = {{ feature_set|safe }};
window.__config = {{ config_set|safe }}; window.__config = {{ config_set|safe }};
window.__oauth = {{ oauth_set|safe }};
window.__token = '{{ csrf_token() }}'; window.__token = '{{ csrf_token() }}';
</script> </script>

81
util/oauth.py Normal file
View file

@ -0,0 +1,81 @@
import urlparse
class OAuthConfig(object):
def __init__(self, app, key_name):
self.key_name = key_name
self.config = app.config.get(key_name, {})
def service_name(self):
raise NotImplementedError
def token_endpoint(self):
raise NotImplementedError
def user_endpoint(self):
raise NotImplementedError
def login_endpoint(self):
raise NotImplementedError
def client_id(self):
return self.config.get('CLIENT_ID')
def client_secret(self):
return self.config.get('CLIENT_SECRET')
def _get_url(self, endpoint, *args):
if not endpoint:
raise Exception('Missing endpoint configuration for OAuth config %s', self.key_name)
for arg in args:
endpoint = urlparse.urljoin(endpoint, arg)
return endpoint
class GithubOAuthConfig(OAuthConfig):
def __init__(self, app, key_name):
super(GithubOAuthConfig, self).__init__(app, key_name)
def service_name(self):
return 'GitHub'
def authorize_endpoint(self):
endpoint = self.config.get('GITHUB_ENDPOINT')
return self._get_url(endpoint, '/login/oauth/authorize') + '?'
def token_endpoint(self):
endpoint = self.config.get('GITHUB_ENDPOINT')
return self._get_url(endpoint, '/login/oauth/access_token')
def _api_endpoint(self):
endpoint = self.config.get('GITHUB_ENDPOINT')
return self.config.get('API_ENDPOINT', self._get_url(endpoint, '/api/v3/'))
def user_endpoint(self):
api_endpoint = self._api_endpoint()
return self._get_url(api_endpoint, 'user')
def email_endpoint(self):
api_endpoint = self._api_endpoint()
return self._get_url(api_endpoint, 'user/emails')
class GoogleOAuthConfig(OAuthConfig):
def __init__(self, app, key_name):
super(GoogleOAuthConfig, self).__init__(app, key_name)
def service_name(self):
return 'Google'
def authorize_endpoint(self):
return 'https://accounts.google.com/o/oauth2/auth?response_type=code&'
def token_endpoint(self):
return 'https://accounts.google.com/o/oauth2/token'
def user_endpoint(self):
return 'https://www.googleapis.com/oauth2/v1/userinfo'