2014-02-18 20:50:15 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from flask import request, redirect, url_for, Blueprint
|
2014-03-26 19:52:24 +00:00
|
|
|
from flask.ext.login import current_user
|
2014-02-18 20:50:15 +00:00
|
|
|
|
2014-04-06 04:50:30 +00:00
|
|
|
from endpoints.common import render_page_template, common_login, route_show_if
|
2014-09-04 21:54:51 +00:00
|
|
|
from app import app, analytics, get_app_url
|
2014-02-18 20:50:15 +00:00
|
|
|
from data import model
|
|
|
|
from util.names import parse_repository_name
|
2014-08-11 19:47:44 +00:00
|
|
|
from util.validation import generate_valid_usernames
|
2014-02-19 21:08:33 +00:00
|
|
|
from util.http import abort
|
|
|
|
from auth.permissions import AdministerRepositoryPermission
|
2014-03-26 19:52:24 +00:00
|
|
|
from auth.auth import require_session_login
|
2014-08-11 22:25:01 +00:00
|
|
|
from peewee import IntegrityError
|
2014-02-18 20:50:15 +00:00
|
|
|
|
2014-04-06 04:50:30 +00:00
|
|
|
import features
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2014-02-18 23:09:14 +00:00
|
|
|
client = app.config['HTTPCLIENT']
|
|
|
|
|
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
callback = Blueprint('callback', __name__)
|
|
|
|
|
2014-09-04 21:54:51 +00:00
|
|
|
def render_ologin_error(service_name,
|
|
|
|
error_message='Could not load user data. The token may have expired.'):
|
|
|
|
return render_page_template('ologinerror.html', service_name=service_name,
|
|
|
|
error_message=error_message,
|
2014-10-02 18:49:18 +00:00
|
|
|
service_url=get_app_url(),
|
|
|
|
user_creation=features.USER_CREATION)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
def exchange_code_for_token(code, service_name='GITHUB', for_login=True, form_encode=False,
|
|
|
|
redirect_suffix=''):
|
2014-02-18 20:50:15 +00:00
|
|
|
code = request.args.get('code')
|
2014-08-11 19:47:44 +00:00
|
|
|
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'
|
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
payload = {
|
2014-08-11 19:47:44 +00:00
|
|
|
'client_id': app.config[id_config],
|
|
|
|
'client_secret': app.config[secret_config],
|
2014-02-18 20:50:15 +00:00
|
|
|
'code': code,
|
2014-08-11 19:47:44 +00:00
|
|
|
'grant_type': 'authorization_code',
|
2014-08-11 22:25:01 +00:00
|
|
|
'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app.config['PREFERRED_URL_SCHEME'],
|
|
|
|
app.config['SERVER_HOSTNAME'],
|
|
|
|
service_name.lower(),
|
|
|
|
redirect_suffix)
|
2014-02-18 20:50:15 +00:00
|
|
|
}
|
2014-08-11 19:47:44 +00:00
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
headers = {
|
|
|
|
'Accept': 'application/json'
|
|
|
|
}
|
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
if form_encode:
|
|
|
|
get_access_token = client.post(app.config[service_name + '_TOKEN_URL'],
|
|
|
|
data=payload, headers=headers)
|
|
|
|
else:
|
|
|
|
get_access_token = client.post(app.config[service_name + '_TOKEN_URL'],
|
|
|
|
params=payload, headers=headers)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
2014-05-23 19:20:40 +00:00
|
|
|
json_data = get_access_token.json()
|
|
|
|
if not json_data:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
token = json_data.get('access_token', '')
|
2014-02-18 20:50:15 +00:00
|
|
|
return token
|
|
|
|
|
|
|
|
|
|
|
|
def get_github_user(token):
|
|
|
|
token_param = {
|
|
|
|
'access_token': token,
|
|
|
|
}
|
2014-02-18 23:09:14 +00:00
|
|
|
get_user = client.get(app.config['GITHUB_USER_URL'], params=token_param)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
return get_user.json()
|
|
|
|
|
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
def get_google_user(token):
|
|
|
|
token_param = {
|
|
|
|
'access_token': token,
|
|
|
|
'alt': 'json',
|
|
|
|
}
|
|
|
|
|
|
|
|
get_user = client.get(app.config['GOOGLE_USER_URL'], params=token_param)
|
|
|
|
return get_user.json()
|
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
def conduct_oauth_login(service_name, user_id, username, email, metadata={}):
|
2014-08-11 19:47:44 +00:00
|
|
|
to_login = model.verify_federated_login(service_name.lower(), user_id)
|
|
|
|
if not to_login:
|
2014-10-02 18:49:18 +00:00
|
|
|
# See if we can create a new user.
|
|
|
|
if not features.USER_CREATION:
|
|
|
|
error_message = 'User creation is disabled. Please contact your administrator'
|
|
|
|
return render_ologin_error(service_name, error_message)
|
|
|
|
|
|
|
|
# Try to create the user
|
2014-08-11 19:47:44 +00:00
|
|
|
try:
|
|
|
|
valid = next(generate_valid_usernames(username))
|
|
|
|
to_login = model.create_federated_user(valid, email, service_name.lower(),
|
2014-08-11 22:25:01 +00:00
|
|
|
user_id, set_password_notification=True,
|
|
|
|
metadata=metadata)
|
2014-08-11 19:47:44 +00:00
|
|
|
|
|
|
|
# Success, tell analytics
|
|
|
|
analytics.track(to_login.username, 'register', {'service': service_name.lower()})
|
|
|
|
|
|
|
|
state = request.args.get('state', None)
|
|
|
|
if state:
|
|
|
|
logger.debug('Aliasing with state: %s' % state)
|
|
|
|
analytics.alias(to_login.username, state)
|
|
|
|
|
2014-10-17 15:44:31 +00:00
|
|
|
except model.InvalidEmailAddressException as ieex:
|
|
|
|
message = "The e-mail address %s is already associated " % (email, )
|
|
|
|
message = message + "with an existing %s account." % (app.config['REGISTRY_TITLE_SHORT'], )
|
|
|
|
message = message + "\nPlease log in with your username and password and "
|
|
|
|
message = message + "associate your %s account to use it in the future." % (service_name, )
|
|
|
|
|
|
|
|
return render_ologin_error(service_name, message)
|
|
|
|
|
|
|
|
except model.DataModelException as ex:
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error(service_name, ex.message)
|
2014-08-11 19:47:44 +00:00
|
|
|
|
|
|
|
if common_login(to_login):
|
|
|
|
return redirect(url_for('web.index'))
|
|
|
|
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error(service_name)
|
2014-08-11 19:47:44 +00:00
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
def get_google_username(user_data):
|
|
|
|
username = user_data['email']
|
|
|
|
at = username.find('@')
|
|
|
|
if at > 0:
|
|
|
|
username = username[0:at]
|
|
|
|
|
|
|
|
return username
|
|
|
|
|
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
@callback.route('/google/callback', methods=['GET'])
|
|
|
|
@route_show_if(features.GOOGLE_LOGIN)
|
|
|
|
def google_oauth_callback():
|
|
|
|
error = request.args.get('error', None)
|
|
|
|
if error:
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('Google', error)
|
2014-08-11 19:47:44 +00:00
|
|
|
|
|
|
|
token = exchange_code_for_token(request.args.get('code'), service_name='GOOGLE', form_encode=True)
|
|
|
|
user_data = get_google_user(token)
|
|
|
|
if not user_data or not user_data.get('id', None) or not user_data.get('email', None):
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('Google')
|
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
username = get_google_username(user_data)
|
|
|
|
metadata = {
|
2014-09-04 21:54:51 +00:00
|
|
|
'service_username': user_data['email']
|
2014-08-11 22:25:01 +00:00
|
|
|
}
|
2014-08-11 19:47:44 +00:00
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
return conduct_oauth_login('Google', user_data['id'], username, user_data['email'],
|
|
|
|
metadata=metadata)
|
2014-08-11 19:47:44 +00:00
|
|
|
|
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
@callback.route('/github/callback', methods=['GET'])
|
2014-04-06 04:50:30 +00:00
|
|
|
@route_show_if(features.GITHUB_LOGIN)
|
2014-02-18 20:50:15 +00:00
|
|
|
def github_oauth_callback():
|
|
|
|
error = request.args.get('error', None)
|
|
|
|
if error:
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('GitHub', error)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB')
|
2014-02-18 20:50:15 +00:00
|
|
|
user_data = get_github_user(token)
|
2014-10-02 18:49:18 +00:00
|
|
|
if not user_data or not 'login' in user_data:
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('GitHub')
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
username = user_data['login']
|
|
|
|
github_id = user_data['id']
|
|
|
|
|
|
|
|
v3_media_type = {
|
|
|
|
'Accept': 'application/vnd.github.v3'
|
|
|
|
}
|
|
|
|
|
|
|
|
token_param = {
|
|
|
|
'access_token': token,
|
|
|
|
}
|
2014-02-18 23:09:14 +00:00
|
|
|
get_email = client.get(app.config['GITHUB_USER_EMAILS'], params=token_param,
|
|
|
|
headers=v3_media_type)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
# We will accept any email, but we prefer the primary
|
|
|
|
found_email = None
|
|
|
|
for user_email in get_email.json():
|
|
|
|
found_email = user_email['email']
|
|
|
|
if user_email['primary']:
|
|
|
|
break
|
|
|
|
|
2014-08-11 22:25:01 +00:00
|
|
|
metadata = {
|
|
|
|
'service_username': username
|
|
|
|
}
|
|
|
|
|
|
|
|
return conduct_oauth_login('github', github_id, username, found_email, metadata=metadata)
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
@callback.route('/google/callback/attach', methods=['GET'])
|
|
|
|
@route_show_if(features.GOOGLE_LOGIN)
|
|
|
|
@require_session_login
|
|
|
|
def google_oauth_attach():
|
2014-08-11 22:25:01 +00:00
|
|
|
token = exchange_code_for_token(request.args.get('code'), service_name='GOOGLE',
|
|
|
|
redirect_suffix='/attach', form_encode=True)
|
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
user_data = get_google_user(token)
|
|
|
|
if not user_data or not user_data.get('id', None):
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('Google')
|
2014-08-11 19:47:44 +00:00
|
|
|
|
|
|
|
google_id = user_data['id']
|
|
|
|
user_obj = current_user.db_user()
|
2014-08-11 22:25:01 +00:00
|
|
|
|
|
|
|
username = get_google_username(user_data)
|
|
|
|
metadata = {
|
2014-09-04 21:54:51 +00:00
|
|
|
'service_username': user_data['email']
|
2014-08-11 22:25:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
model.attach_federated_login(user_obj, 'google', google_id, metadata=metadata)
|
|
|
|
except IntegrityError:
|
|
|
|
err = 'Google account %s is already attached to a %s account' % (
|
|
|
|
username, app.config['REGISTRY_TITLE_SHORT'])
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('Google', err)
|
2014-08-11 22:25:01 +00:00
|
|
|
|
2014-08-11 19:47:44 +00:00
|
|
|
return redirect(url_for('web.user'))
|
2014-02-18 20:50:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
@callback.route('/github/callback/attach', methods=['GET'])
|
2014-04-06 04:50:30 +00:00
|
|
|
@route_show_if(features.GITHUB_LOGIN)
|
2014-03-26 19:52:24 +00:00
|
|
|
@require_session_login
|
2014-02-18 20:50:15 +00:00
|
|
|
def github_oauth_attach():
|
2014-08-11 19:47:44 +00:00
|
|
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB')
|
2014-02-18 20:50:15 +00:00
|
|
|
user_data = get_github_user(token)
|
2014-06-04 20:12:31 +00:00
|
|
|
if not user_data:
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('GitHub')
|
2014-06-04 20:12:31 +00:00
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
github_id = user_data['id']
|
|
|
|
user_obj = current_user.db_user()
|
2014-08-11 22:25:01 +00:00
|
|
|
|
|
|
|
username = user_data['login']
|
|
|
|
metadata = {
|
|
|
|
'service_username': username
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
model.attach_federated_login(user_obj, 'github', github_id, metadata=metadata)
|
|
|
|
except IntegrityError:
|
|
|
|
err = 'Github account %s is already attached to a %s account' % (
|
|
|
|
username, app.config['REGISTRY_TITLE_SHORT'])
|
|
|
|
|
2014-09-04 21:54:51 +00:00
|
|
|
return render_ologin_error('GitHub', err)
|
2014-08-11 22:25:01 +00:00
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
return redirect(url_for('web.user'))
|
|
|
|
|
|
|
|
|
|
|
|
@callback.route('/github/callback/trigger/<path:repository>', methods=['GET'])
|
2014-05-30 22:28:18 +00:00
|
|
|
@route_show_if(features.GITHUB_BUILD)
|
2014-03-26 19:52:24 +00:00
|
|
|
@require_session_login
|
2014-02-18 20:50:15 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def attach_github_build_trigger(namespace, repository):
|
2014-02-19 21:08:33 +00:00
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
2014-08-11 22:25:01 +00:00
|
|
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB',
|
|
|
|
for_login=False)
|
2014-02-19 21:08:33 +00:00
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if not repo:
|
|
|
|
msg = 'Invalid repository: %s/%s' % (namespace, repository)
|
|
|
|
abort(404, message=msg)
|
|
|
|
|
2014-02-20 23:57:49 +00:00
|
|
|
trigger = model.create_build_trigger(repo, 'github', token, current_user.db_user())
|
2014-02-19 21:08:33 +00:00
|
|
|
admin_path = '%s/%s/%s' % (namespace, repository, 'admin')
|
2014-03-26 19:52:24 +00:00
|
|
|
full_url = '%s%s%s' % (url_for('web.repository', path=admin_path), '?tab=trigger&new_trigger=',
|
|
|
|
trigger.uuid)
|
2014-02-19 21:08:33 +00:00
|
|
|
logger.debug('Redirecting to full url: %s' % full_url)
|
|
|
|
return redirect(full_url)
|
|
|
|
|
2014-02-20 23:57:49 +00:00
|
|
|
abort(403)
|