Merge pull request #447 from coreos-inc/ronon

Add support for Dex to Quay
This commit is contained in:
josephschorr 2015-09-10 11:42:01 -04:00
commit edef283697
27 changed files with 533 additions and 176 deletions

View file

@ -306,6 +306,7 @@ class User(ApiResource):
return user_view(user)
@show_if(features.USER_CREATION)
@show_if(features.DIRECT_LOGIN)
@nickname('createNewUser')
@internal_only
@validate_json_request('NewUser')
@ -496,6 +497,7 @@ class ConvertToOrganization(ApiResource):
@resource('/v1/signin')
@show_if(features.DIRECT_LOGIN)
@internal_only
class Signin(ApiResource):
""" Operations for signing in the user. """
@ -595,6 +597,7 @@ class Signout(ApiResource):
@resource('/v1/detachexternal/<servicename>')
@show_if(features.DIRECT_LOGIN)
@internal_only
class DetachExternal(ApiResource):
""" Resource for detaching an external login. """

View file

@ -5,7 +5,7 @@ from flask import request, redirect, url_for, Blueprint
from flask.ext.login import current_user
from endpoints.common import render_page_template, common_login, route_show_if
from app import app, analytics, get_app_url, github_login, google_login
from app import app, analytics, get_app_url, github_login, google_login, dex_login
from data import model
from util.names import parse_repository_name
from util.validation import generate_valid_usernames
@ -14,6 +14,7 @@ from auth.auth import require_session_login
from peewee import IntegrityError
import features
from util.security.strictjwt import decode, InvalidTokenError
logger = logging.getLogger(__name__)
client = app.config['HTTPCLIENT']
@ -24,7 +25,7 @@ def render_ologin_error(service_name,
return render_page_template('ologinerror.html', service_name=service_name,
error_message=error_message,
service_url=get_app_url(),
user_creation=features.USER_CREATION)
user_creation=features.USER_CREATION and features.DIRECT_LOGIN)
def get_user(service, token):
@ -86,7 +87,7 @@ def conduct_oauth_login(service, user_id, username, email, metadata={}):
return render_ologin_error(service_name)
def get_google_username(user_data):
def get_email_username(user_data):
username = user_data['email']
at = username.find('@')
if at > 0:
@ -108,7 +109,7 @@ def google_oauth_callback():
if not user_data or not user_data.get('id', None) or not user_data.get('email', None):
return render_ologin_error('Google')
username = get_google_username(user_data)
username = get_email_username(user_data)
metadata = {
'service_username': user_data['email']
}
@ -194,7 +195,7 @@ def google_oauth_attach():
google_id = user_data['id']
user_obj = current_user.db_user()
username = get_google_username(user_data)
username = get_email_username(user_data)
metadata = {
'service_username': user_data['email']
}
@ -236,3 +237,83 @@ def github_oauth_attach():
return render_ologin_error('GitHub', err)
return redirect(url_for('web.user'))
def decode_user_jwt(token, oidc_provider):
try:
return decode(token, oidc_provider.get_public_key(), algorithms=['RS256'],
audience=oidc_provider.client_id(),
issuer=oidc_provider.issuer)
except InvalidTokenError:
# Public key may have expired. Try to retrieve an updated public key and use it to decode.
return decode(token, oidc_provider.get_public_key(force_refresh=True), algorithms=['RS256'],
audience=oidc_provider.client_id(),
issuer=oidc_provider.issuer)
@oauthlogin.route('/dex/callback', methods=['GET', 'POST'])
@route_show_if(features.DEX_LOGIN)
def dex_oauth_callback():
error = request.values.get('error', None)
if error:
return render_ologin_error(dex_login.public_title, error)
code = request.values.get('code')
if not code:
return render_ologin_error(dex_login.public_title, 'Missing OAuth code')
token = dex_login.exchange_code_for_token(app.config, client, code, client_auth=True,
form_encode=True)
try:
payload = decode_user_jwt(token, dex_login)
except InvalidTokenError:
logger.exception('Exception when decoding returned JWT')
return render_ologin_error(dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.')
username = get_email_username(payload)
metadata = {}
dex_id = payload['sub']
email_address = payload['email']
if not payload.get('email_verified', False):
return render_ologin_error(dex_login.public_title,
'A verified e-mail address is required for login. Please verify your ' +
'e-mail address in %s and try again.' % dex_login.public_title)
return conduct_oauth_login(dex_login, dex_id, username, email_address,
metadata=metadata)
@oauthlogin.route('/dex/callback/attach', methods=['GET', 'POST'])
@route_show_if(features.DEX_LOGIN)
@require_session_login
def dex_oauth_attach():
code = request.args.get('code')
token = dex_login.exchange_code_for_token(app.config, client, code, redirect_suffix='/attach',
client_auth=True, form_encode=True)
if not token:
return render_ologin_error(dex_login.public_title)
try:
payload = decode_user_jwt(token, dex_login)
except jwt.InvalidTokenError:
logger.exception('Exception when decoding returned JWT')
return render_ologin_error(dex_login.public_title,
'Could not decode response. Please contact your system administrator about this error.')
user_obj = current_user.db_user()
dex_id = payload['sub']
metadata = {}
try:
model.user.attach_federated_login(user_obj, 'dex', dex_id, metadata=metadata)
except IntegrityError:
err = '%s account is already attached to a %s account' % (dex_login.public_title,
app.config['REGISTRY_TITLE_SHORT'])
return render_ologin_error(dex_login.public_title, err)
return redirect(url_for('web.user'))