Merge remote-tracking branch 'origin/zegooglesdosomething'
This commit is contained in:
commit
3c57e612b3
14 changed files with 350 additions and 96 deletions
12
config.py
12
config.py
|
@ -19,7 +19,7 @@ def build_requests_session():
|
||||||
CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'GITHUB_CLIENT_ID',
|
CLIENT_WHITELIST = ['SERVER_HOSTNAME', 'PREFERRED_URL_SCHEME', 'GITHUB_CLIENT_ID',
|
||||||
'GITHUB_LOGIN_CLIENT_ID', 'MIXPANEL_KEY', 'STRIPE_PUBLISHABLE_KEY',
|
'GITHUB_LOGIN_CLIENT_ID', 'MIXPANEL_KEY', 'STRIPE_PUBLISHABLE_KEY',
|
||||||
'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN', 'AUTHENTICATION_TYPE',
|
'ENTERPRISE_LOGO_URL', 'SENTRY_PUBLIC_DSN', 'AUTHENTICATION_TYPE',
|
||||||
'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT']
|
'REGISTRY_TITLE', 'REGISTRY_TITLE_SHORT', 'GOOGLE_LOGIN_CLIENT_ID']
|
||||||
|
|
||||||
|
|
||||||
def getFrontendVisibleConfig(config_dict):
|
def getFrontendVisibleConfig(config_dict):
|
||||||
|
@ -115,6 +115,13 @@ class DefaultConfig(object):
|
||||||
GITHUB_LOGIN_CLIENT_ID = ''
|
GITHUB_LOGIN_CLIENT_ID = ''
|
||||||
GITHUB_LOGIN_CLIENT_SECRET = ''
|
GITHUB_LOGIN_CLIENT_SECRET = ''
|
||||||
|
|
||||||
|
# Google Config.
|
||||||
|
GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
|
||||||
|
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()
|
||||||
|
|
||||||
|
@ -144,6 +151,9 @@ class DefaultConfig(object):
|
||||||
# Feature Flag: Whether GitHub login is supported.
|
# Feature Flag: Whether GitHub login is supported.
|
||||||
FEATURE_GITHUB_LOGIN = False
|
FEATURE_GITHUB_LOGIN = False
|
||||||
|
|
||||||
|
# Feature Flag: Whether Google login is supported.
|
||||||
|
FEATURE_GOOGLE_LOGIN = False
|
||||||
|
|
||||||
# Feature flag, whether to enable olark chat
|
# Feature flag, whether to enable olark chat
|
||||||
FEATURE_OLARK_CHAT = False
|
FEATURE_OLARK_CHAT = False
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ class FederatedLogin(BaseModel):
|
||||||
user = ForeignKeyField(User, index=True)
|
user = ForeignKeyField(User, index=True)
|
||||||
service = ForeignKeyField(LoginService, index=True)
|
service = ForeignKeyField(LoginService, index=True)
|
||||||
service_ident = CharField()
|
service_ident = CharField()
|
||||||
|
metadata_json = TextField(default='{}')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""add metadata field to external logins
|
||||||
|
|
||||||
|
Revision ID: 1594a74a74ca
|
||||||
|
Revises: f42b0ea7a4d
|
||||||
|
Create Date: 2014-09-04 18:17:35.205698
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '1594a74a74ca'
|
||||||
|
down_revision = 'f42b0ea7a4d'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
from data.model.sqlalchemybridge import gen_sqlalchemy_metadata
|
||||||
|
from data.database import all_models
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('federatedlogin', sa.Column('metadata_json', sa.Text(), nullable=False))
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
schema = gen_sqlalchemy_metadata(all_models)
|
||||||
|
|
||||||
|
op.bulk_insert(schema.tables['loginservice'],
|
||||||
|
[
|
||||||
|
{'id':4, 'name':'google'},
|
||||||
|
])
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('federatedlogin', 'metadata_json')
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
schema = gen_sqlalchemy_metadata(all_models)
|
||||||
|
loginservice = schema.table['loginservice']
|
||||||
|
|
||||||
|
op.execute(
|
||||||
|
(loginservice.delete()
|
||||||
|
.where(loginservice.c.name == op.inline_literal('google')))
|
||||||
|
)
|
|
@ -387,7 +387,8 @@ def set_team_org_permission(team, team_role_name, set_by_username):
|
||||||
return team
|
return team
|
||||||
|
|
||||||
|
|
||||||
def create_federated_user(username, email, service_name, service_id, set_password_notification):
|
def create_federated_user(username, email, service_name, service_id,
|
||||||
|
set_password_notification, metadata={}):
|
||||||
if not is_create_user_allowed():
|
if not is_create_user_allowed():
|
||||||
raise TooManyUsersException()
|
raise TooManyUsersException()
|
||||||
|
|
||||||
|
@ -397,7 +398,8 @@ def create_federated_user(username, email, service_name, service_id, set_passwor
|
||||||
|
|
||||||
service = LoginService.get(LoginService.name == service_name)
|
service = LoginService.get(LoginService.name == service_name)
|
||||||
FederatedLogin.create(user=new_user, service=service,
|
FederatedLogin.create(user=new_user, service=service,
|
||||||
service_ident=service_id)
|
service_ident=service_id,
|
||||||
|
metadata_json=json.dumps(metadata))
|
||||||
|
|
||||||
if set_password_notification:
|
if set_password_notification:
|
||||||
create_notification('password_required', new_user)
|
create_notification('password_required', new_user)
|
||||||
|
@ -405,9 +407,10 @@ def create_federated_user(username, email, service_name, service_id, set_passwor
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
|
|
||||||
def attach_federated_login(user, service_name, service_id):
|
def attach_federated_login(user, service_name, service_id, metadata={}):
|
||||||
service = LoginService.get(LoginService.name == service_name)
|
service = LoginService.get(LoginService.name == service_name)
|
||||||
FederatedLogin.create(user=user, service=service, service_ident=service_id)
|
FederatedLogin.create(user=user, service=service, service_ident=service_id,
|
||||||
|
metadata_json=json.dumps(metadata))
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@ -426,7 +429,7 @@ def verify_federated_login(service_name, service_id):
|
||||||
|
|
||||||
def list_federated_logins(user):
|
def list_federated_logins(user):
|
||||||
selected = FederatedLogin.select(FederatedLogin.service_ident,
|
selected = FederatedLogin.select(FederatedLogin.service_ident,
|
||||||
LoginService.name)
|
LoginService.name, FederatedLogin.metadata_json)
|
||||||
joined = selected.join(LoginService)
|
joined = selected.join(LoginService)
|
||||||
return joined.where(LoginService.name != 'quayrobot',
|
return joined.where(LoginService.name != 'quayrobot',
|
||||||
FederatedLogin.user == user)
|
FederatedLogin.user == user)
|
||||||
|
|
|
@ -40,9 +40,15 @@ def user_view(user):
|
||||||
organizations = model.get_user_organizations(user.username)
|
organizations = model.get_user_organizations(user.username)
|
||||||
|
|
||||||
def login_view(login):
|
def login_view(login):
|
||||||
|
try:
|
||||||
|
metadata = json.loads(login.metadata_json)
|
||||||
|
except:
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'service': login.service.name,
|
'service': login.service.name,
|
||||||
'service_identifier': login.service_ident,
|
'service_identifier': login.service_ident,
|
||||||
|
'metadata': metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
logins = model.list_federated_logins(user)
|
logins = model.list_federated_logins(user)
|
||||||
|
@ -89,6 +95,7 @@ class User(ApiResource):
|
||||||
""" Operations related to users. """
|
""" Operations related to users. """
|
||||||
schemas = {
|
schemas = {
|
||||||
'NewUser': {
|
'NewUser': {
|
||||||
|
|
||||||
'id': 'NewUser',
|
'id': 'NewUser',
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'description': 'Fields which must be specified for a new user.',
|
'description': 'Fields which must be specified for a new user.',
|
||||||
|
|
|
@ -4,12 +4,14 @@ 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
|
from app import app, analytics, get_app_url
|
||||||
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.http import abort
|
from util.http import abort
|
||||||
from auth.permissions import AdministerRepositoryPermission
|
from auth.permissions import AdministerRepositoryPermission
|
||||||
from auth.auth import require_session_login
|
from auth.auth import require_session_login
|
||||||
|
from peewee import IntegrityError
|
||||||
|
|
||||||
import features
|
import features
|
||||||
|
|
||||||
|
@ -20,20 +22,39 @@ client = app.config['HTTPCLIENT']
|
||||||
|
|
||||||
callback = Blueprint('callback', __name__)
|
callback = Blueprint('callback', __name__)
|
||||||
|
|
||||||
|
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,
|
||||||
|
service_url=get_app_url())
|
||||||
|
|
||||||
def exchange_github_code_for_token(code, for_login=True):
|
def exchange_code_for_token(code, service_name='GITHUB', for_login=True, form_encode=False,
|
||||||
|
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['GITHUB_LOGIN_CLIENT_ID' if for_login else 'GITHUB_CLIENT_ID'],
|
'client_id': app.config[id_config],
|
||||||
'client_secret': app.config['GITHUB_LOGIN_CLIENT_SECRET' if for_login else 'GITHUB_CLIENT_SECRET'],
|
'client_secret': app.config[secret_config],
|
||||||
'code': code,
|
'code': code,
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'redirect_uri': '%s://%s/oauth2/%s/callback%s' % (app.config['PREFERRED_URL_SCHEME'],
|
||||||
|
app.config['SERVER_HOSTNAME'],
|
||||||
|
service_name.lower(),
|
||||||
|
redirect_suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
get_access_token = client.post(app.config['GITHUB_TOKEN_URL'],
|
if form_encode:
|
||||||
params=payload, headers=headers)
|
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)
|
||||||
|
|
||||||
json_data = get_access_token.json()
|
json_data = get_access_token.json()
|
||||||
if not json_data:
|
if not json_data:
|
||||||
|
@ -52,17 +73,82 @@ def get_github_user(token):
|
||||||
return get_user.json()
|
return get_user.json()
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
def conduct_oauth_login(service_name, user_id, username, email, metadata={}):
|
||||||
|
to_login = model.verify_federated_login(service_name.lower(), user_id)
|
||||||
|
if not to_login:
|
||||||
|
# try to create the user
|
||||||
|
try:
|
||||||
|
valid = next(generate_valid_usernames(username))
|
||||||
|
to_login = model.create_federated_user(valid, email, service_name.lower(),
|
||||||
|
user_id, set_password_notification=True,
|
||||||
|
metadata=metadata)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
except model.DataModelException, ex:
|
||||||
|
return render_ologin_error(service_name, ex.message)
|
||||||
|
|
||||||
|
if common_login(to_login):
|
||||||
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
|
return render_ologin_error(service_name)
|
||||||
|
|
||||||
|
def get_google_username(user_data):
|
||||||
|
username = user_data['email']
|
||||||
|
at = username.find('@')
|
||||||
|
if at > 0:
|
||||||
|
username = username[0:at]
|
||||||
|
|
||||||
|
return username
|
||||||
|
|
||||||
|
|
||||||
|
@callback.route('/google/callback', methods=['GET'])
|
||||||
|
@route_show_if(features.GOOGLE_LOGIN)
|
||||||
|
def google_oauth_callback():
|
||||||
|
error = request.args.get('error', None)
|
||||||
|
if error:
|
||||||
|
return render_ologin_error('Google', error)
|
||||||
|
|
||||||
|
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):
|
||||||
|
return render_ologin_error('Google')
|
||||||
|
|
||||||
|
username = get_google_username(user_data)
|
||||||
|
metadata = {
|
||||||
|
'service_username': user_data['email']
|
||||||
|
}
|
||||||
|
|
||||||
|
return conduct_oauth_login('Google', user_data['id'], username, user_data['email'],
|
||||||
|
metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/github/callback', methods=['GET'])
|
@callback.route('/github/callback', methods=['GET'])
|
||||||
@route_show_if(features.GITHUB_LOGIN)
|
@route_show_if(features.GITHUB_LOGIN)
|
||||||
def github_oauth_callback():
|
def github_oauth_callback():
|
||||||
error = request.args.get('error', None)
|
error = request.args.get('error', None)
|
||||||
if error:
|
if error:
|
||||||
return render_page_template('githuberror.html', error_message=error)
|
return render_ologin_error('GitHub', error)
|
||||||
|
|
||||||
token = exchange_github_code_for_token(request.args.get('code'))
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB')
|
||||||
user_data = get_github_user(token)
|
user_data = get_github_user(token)
|
||||||
if not user_data:
|
if not user_data:
|
||||||
return render_page_template('githuberror.html', error_message='Could not load user data')
|
return render_ologin_error('GitHub')
|
||||||
|
|
||||||
username = user_data['login']
|
username = user_data['login']
|
||||||
github_id = user_data['id']
|
github_id = user_data['id']
|
||||||
|
@ -84,42 +170,67 @@ def github_oauth_callback():
|
||||||
if user_email['primary']:
|
if user_email['primary']:
|
||||||
break
|
break
|
||||||
|
|
||||||
to_login = model.verify_federated_login('github', github_id)
|
metadata = {
|
||||||
if not to_login:
|
'service_username': username
|
||||||
# try to create the user
|
}
|
||||||
try:
|
|
||||||
to_login = model.create_federated_user(username, found_email, 'github',
|
|
||||||
github_id, set_password_notification=True)
|
|
||||||
|
|
||||||
# Success, tell analytics
|
return conduct_oauth_login('github', github_id, username, found_email, metadata=metadata)
|
||||||
analytics.track(to_login.username, 'register', {'service': 'github'})
|
|
||||||
|
|
||||||
state = request.args.get('state', None)
|
|
||||||
if state:
|
|
||||||
logger.debug('Aliasing with state: %s' % state)
|
|
||||||
analytics.alias(to_login.username, state)
|
|
||||||
|
|
||||||
except model.DataModelException, ex:
|
@callback.route('/google/callback/attach', methods=['GET'])
|
||||||
return render_page_template('githuberror.html', error_message=ex.message)
|
@route_show_if(features.GOOGLE_LOGIN)
|
||||||
|
@require_session_login
|
||||||
|
def google_oauth_attach():
|
||||||
|
token = exchange_code_for_token(request.args.get('code'), service_name='GOOGLE',
|
||||||
|
redirect_suffix='/attach', form_encode=True)
|
||||||
|
|
||||||
if common_login(to_login):
|
user_data = get_google_user(token)
|
||||||
return redirect(url_for('web.index'))
|
if not user_data or not user_data.get('id', None):
|
||||||
|
return render_ologin_error('Google')
|
||||||
|
|
||||||
return render_page_template('githuberror.html')
|
google_id = user_data['id']
|
||||||
|
user_obj = current_user.db_user()
|
||||||
|
|
||||||
|
username = get_google_username(user_data)
|
||||||
|
metadata = {
|
||||||
|
'service_username': user_data['email']
|
||||||
|
}
|
||||||
|
|
||||||
|
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'])
|
||||||
|
return render_ologin_error('Google', err)
|
||||||
|
|
||||||
|
return redirect(url_for('web.user'))
|
||||||
|
|
||||||
|
|
||||||
@callback.route('/github/callback/attach', methods=['GET'])
|
@callback.route('/github/callback/attach', methods=['GET'])
|
||||||
@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_github_code_for_token(request.args.get('code'))
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB')
|
||||||
user_data = get_github_user(token)
|
user_data = get_github_user(token)
|
||||||
if not user_data:
|
if not user_data:
|
||||||
return render_page_template('githuberror.html', error_message='Could not load user data')
|
return render_ologin_error('GitHub')
|
||||||
|
|
||||||
github_id = user_data['id']
|
github_id = user_data['id']
|
||||||
user_obj = current_user.db_user()
|
user_obj = current_user.db_user()
|
||||||
model.attach_federated_login(user_obj, 'github', github_id)
|
|
||||||
|
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'])
|
||||||
|
|
||||||
|
return render_ologin_error('GitHub', err)
|
||||||
|
|
||||||
return redirect(url_for('web.user'))
|
return redirect(url_for('web.user'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,7 +241,8 @@ 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_github_code_for_token(request.args.get('code'), for_login=False)
|
token = exchange_code_for_token(request.args.get('code'), service_name='GITHUB',
|
||||||
|
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)
|
||||||
|
|
|
@ -179,6 +179,8 @@ def initialize_database():
|
||||||
TeamRole.create(name='member')
|
TeamRole.create(name='member')
|
||||||
Visibility.create(name='public')
|
Visibility.create(name='public')
|
||||||
Visibility.create(name='private')
|
Visibility.create(name='private')
|
||||||
|
|
||||||
|
LoginService.create(name='google')
|
||||||
LoginService.create(name='github')
|
LoginService.create(name='github')
|
||||||
LoginService.create(name='quayrobot')
|
LoginService.create(name='quayrobot')
|
||||||
LoginService.create(name='ldap')
|
LoginService.create(name='ldap')
|
||||||
|
|
17
static/directives/external-login-button.html
Normal file
17
static/directives/external-login-button.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<span class="external-login-button-element">
|
||||||
|
<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">
|
||||||
|
<i class="fa fa-github fa-lg"></i>
|
||||||
|
<span ng-if="action != 'attach'">Sign In with GitHub</span>
|
||||||
|
<span ng-if="action == 'attach'">Attach to GitHub Account</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span ng-if="provider == 'google'">
|
||||||
|
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GOOGLE_LOGIN']" ng-click="startSignin('google')">
|
||||||
|
<i class="fa fa-google fa-lg"></i>
|
||||||
|
<span ng-if="action != 'attach'">Sign In with Google</span>
|
||||||
|
<span ng-if="action == 'attach'">Attach to Google Account</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</span>
|
|
@ -12,15 +12,13 @@
|
||||||
<span ng-show="tryAgainSoon == 0">
|
<span ng-show="tryAgainSoon == 0">
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
||||||
|
|
||||||
<span class="social-alternate" quay-require="['GITHUB_LOGIN']">
|
<span class="social-alternate" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">
|
||||||
<i class="fa fa-circle"></i>
|
<i class="fa fa-circle"></i>
|
||||||
<span class="inner-text">OR</span>
|
<span class="inner-text">OR</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<a id="github-signin-link" class="btn btn-primary btn-lg btn-block" href="javascript:void(0)" ng-click="showGithub()"
|
<div class="external-login-button" provider="github" redirect-url="redirectUrl" sign-in-started="markStarted()"></div>
|
||||||
quay-require="['GITHUB_LOGIN']">
|
<div class="external-login-button" provider="google" redirect-url="redirectUrl" sign-in-started="markStarted()"></div>
|
||||||
<i class="fa fa-github fa-lg"></i> Sign In with GitHub
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,8 @@
|
||||||
<i class="fa fa-circle"></i>
|
<i class="fa fa-circle"></i>
|
||||||
<span class="inner-text">OR</span>
|
<span class="inner-text">OR</span>
|
||||||
</span>
|
</span>
|
||||||
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}"
|
<div class="external-login-button" provider="github"></div>
|
||||||
class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']">
|
<div class="external-login-button" provider="google"></div>
|
||||||
<i class="fa fa-github fa-lg"></i> Sign In with GitHub
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div ng-show="registering" style="text-align: center">
|
<div ng-show="registering" style="text-align: center">
|
||||||
|
|
|
@ -1424,10 +1424,41 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
var keyService = {}
|
var keyService = {}
|
||||||
|
|
||||||
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
|
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
|
||||||
|
|
||||||
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
|
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
|
||||||
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
|
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
|
||||||
keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback');
|
keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback');
|
||||||
|
|
||||||
|
keyService['googleLoginClientId'] = Config['GOOGLE_LOGIN_CLIENT_ID'];
|
||||||
|
keyService['googleRedirectUri'] = Config.getUrl('/oauth2/google/callback');
|
||||||
|
|
||||||
|
keyService['googleLoginUrl'] = 'https://accounts.google.com/o/oauth2/auth?response_type=code&';
|
||||||
|
keyService['githubLoginUrl'] = 'https://github.com/login/oauth/authorize?';
|
||||||
|
|
||||||
|
keyService['googleLoginScope'] = 'openid email';
|
||||||
|
keyService['githubLoginScope'] = 'user:email';
|
||||||
|
|
||||||
|
keyService.getExternalLoginUrl = function(service, action) {
|
||||||
|
var state_clause = '';
|
||||||
|
if (Config.MIXPANEL_KEY && window.mixpanel) {
|
||||||
|
if (mixpanel.get_distinct_id !== undefined) {
|
||||||
|
state_clause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var client_id = keyService[service + 'LoginClientId'];
|
||||||
|
var scope = keyService[service + 'LoginScope'];
|
||||||
|
var redirect_uri = keyService[service + 'RedirectUri'];
|
||||||
|
if (action == 'attach') {
|
||||||
|
redirect_uri += '/attach';
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = keyService[service + 'LoginUrl'] + 'client_id=' + client_id + '&scope=' + scope +
|
||||||
|
'&redirect_uri=' + redirect_uri + state_clause;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
return keyService;
|
return keyService;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -2312,6 +2343,41 @@ quayApp.directive('userSetup', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
quayApp.directive('externalLoginButton', function () {
|
||||||
|
var directiveDefinitionObject = {
|
||||||
|
priority: 0,
|
||||||
|
templateUrl: '/static/directives/external-login-button.html',
|
||||||
|
replace: false,
|
||||||
|
transclude: true,
|
||||||
|
restrict: 'C',
|
||||||
|
scope: {
|
||||||
|
'signInStarted': '&signInStarted',
|
||||||
|
'redirectUrl': '=redirectUrl',
|
||||||
|
'provider': '@provider',
|
||||||
|
'action': '@action'
|
||||||
|
},
|
||||||
|
controller: function($scope, $timeout, $interval, ApiService, KeyService, CookieService, Features, Config) {
|
||||||
|
$scope.startSignin = function(service) {
|
||||||
|
$scope.signInStarted({'service': service});
|
||||||
|
|
||||||
|
var url = KeyService.getExternalLoginUrl(service, $scope.action || 'login');
|
||||||
|
|
||||||
|
// Save the redirect URL in a cookie so that we can redirect back after the service returns to us.
|
||||||
|
var redirectURL = $scope.redirectUrl || window.location.toString();
|
||||||
|
CookieService.putPermanent('quay.redirectAfterLoad', redirectURL);
|
||||||
|
|
||||||
|
// Needed to ensure that UI work done by the started callback is finished before the location
|
||||||
|
// changes.
|
||||||
|
$timeout(function() {
|
||||||
|
document.location = url;
|
||||||
|
}, 250);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return directiveDefinitionObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
quayApp.directive('signinForm', function () {
|
quayApp.directive('signinForm', function () {
|
||||||
var directiveDefinitionObject = {
|
var directiveDefinitionObject = {
|
||||||
priority: 0,
|
priority: 0,
|
||||||
|
@ -2328,29 +2394,6 @@ quayApp.directive('signinForm', function () {
|
||||||
$scope.tryAgainSoon = 0;
|
$scope.tryAgainSoon = 0;
|
||||||
$scope.tryAgainInterval = null;
|
$scope.tryAgainInterval = null;
|
||||||
|
|
||||||
$scope.showGithub = function() {
|
|
||||||
if (!Features.GITHUB_LOGIN) { return; }
|
|
||||||
|
|
||||||
$scope.markStarted();
|
|
||||||
|
|
||||||
var mixpanelDistinctIdClause = '';
|
|
||||||
if (Config.MIXPANEL_KEY && mixpanel.get_distinct_id !== undefined) {
|
|
||||||
$scope.mixpanelDistinctIdClause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the redirect URL in a cookie so that we can redirect back after GitHub returns to us.
|
|
||||||
var redirectURL = $scope.redirectUrl || window.location.toString();
|
|
||||||
CookieService.putPermanent('quay.redirectAfterLoad', redirectURL);
|
|
||||||
|
|
||||||
// Needed to ensure that UI work done by the started callback is finished before the location
|
|
||||||
// changes.
|
|
||||||
$timeout(function() {
|
|
||||||
var url = 'https://github.com/login/oauth/authorize?client_id=' + encodeURIComponent(KeyService.githubLoginClientId) +
|
|
||||||
'&scope=user:email' + mixpanelDistinctIdClause;
|
|
||||||
document.location = url;
|
|
||||||
}, 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.markStarted = function() {
|
$scope.markStarted = function() {
|
||||||
if ($scope.signInStarted != null) {
|
if ($scope.signInStarted != null) {
|
||||||
$scope.signInStarted();
|
$scope.signInStarted();
|
||||||
|
@ -2439,15 +2482,6 @@ quayApp.directive('signupForm', function () {
|
||||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, Config, UIService) {
|
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, Config, UIService) {
|
||||||
$('.form-signup').popover();
|
$('.form-signup').popover();
|
||||||
|
|
||||||
if (Config.MIXPANEL_KEY) {
|
|
||||||
angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
|
|
||||||
var mixpanelId = loadedMixpanel.get_distinct_id();
|
|
||||||
$scope.github_state_clause = '&state=' + mixpanelId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.githubClientId = KeyService.githubLoginClientId;
|
|
||||||
|
|
||||||
$scope.awaitingConfirmation = false;
|
$scope.awaitingConfirmation = false;
|
||||||
$scope.registering = false;
|
$scope.registering = false;
|
||||||
|
|
||||||
|
|
|
@ -1645,13 +1645,16 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
||||||
UserService.updateUserIn($scope, function(user) {
|
UserService.updateUserIn($scope, function(user) {
|
||||||
$scope.cuser = jQuery.extend({}, user);
|
$scope.cuser = jQuery.extend({}, user);
|
||||||
|
|
||||||
if (Features.GITHUB_LOGIN && $scope.cuser.logins) {
|
if ($scope.cuser.logins) {
|
||||||
for (var i = 0; i < $scope.cuser.logins.length; i++) {
|
for (var i = 0; i < $scope.cuser.logins.length; i++) {
|
||||||
if ($scope.cuser.logins[i].service == 'github') {
|
if ($scope.cuser.logins[i].service == 'github') {
|
||||||
var githubId = $scope.cuser.logins[i].service_identifier;
|
$scope.hasGithubLogin = true;
|
||||||
$http.get('https://api.github.com/user/' + githubId).success(function(resp) {
|
$scope.githubLogin = $scope.cuser.logins[i].metadata['service_username'];
|
||||||
$scope.githubLogin = resp.login;
|
}
|
||||||
});
|
|
||||||
|
if ($scope.cuser.logins[i].service == 'google') {
|
||||||
|
$scope.hasGoogleLogin = true;
|
||||||
|
$scope.googleLogin = $scope.cuser.logins[i].metadata['service_username'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1669,7 +1672,6 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
||||||
$scope.convertStep = 0;
|
$scope.convertStep = 0;
|
||||||
$scope.org = {};
|
$scope.org = {};
|
||||||
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
||||||
$scope.githubClientId = KeyService.githubLoginClientId;
|
|
||||||
$scope.authorizedApps = null;
|
$scope.authorizedApps = null;
|
||||||
|
|
||||||
$scope.logsShown = 0;
|
$scope.logsShown = 0;
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github" quay-require="['GITHUB_LOGIN']">GitHub Login</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#external" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">External Logins</a></li>
|
||||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
|
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
|
||||||
<li quay-show="Features.USER_LOG_ACCESS || hasPaidBusinessPlan">
|
<li quay-show="Features.USER_LOG_ACCESS || hasPaidBusinessPlan">
|
||||||
<a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a>
|
<a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a>
|
||||||
|
@ -163,25 +163,52 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Github tab -->
|
<!-- External Login tab -->
|
||||||
<div id="github" class="tab-pane" quay-require="['GITHUB_LOGIN']">
|
<div id="external" class="tab-pane" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">
|
||||||
<div class="loading" ng-show="!cuser">
|
<div class="loading" ng-show="!cuser">
|
||||||
<div class="quay-spinner 3x"></div>
|
<div class="quay-spinner 3x"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" ng-show="cuser">
|
|
||||||
|
<!-- Github -->
|
||||||
|
<div class="row" quay-show="cuser && Features.GITHUB_LOGIN">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-title">GitHub Login:</div>
|
<div class="panel-title">GitHub Login:</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div ng-show="githubLogin" class="lead col-md-8">
|
<div ng-show="hasGithubLogin && githubLogin" class="lead col-md-8">
|
||||||
<i class="fa fa-github fa-lg" style="margin-right: 6px;" data-title="GitHub" bs-tooltip="tooltip.title"></i>
|
<i class="fa fa-github fa-lg" style="margin-right: 6px;" data-title="GitHub" bs-tooltip="tooltip.title"></i>
|
||||||
<b><a href="https://github.com/{{githubLogin}}" target="_blank">{{githubLogin}}</a></b>
|
<b><a href="https://github.com/{{githubLogin}}" target="_blank">{{githubLogin}}</a></b>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="!githubLogin" class="col-md-8">
|
<div ng-show="hasGithubLogin && !githubLogin" class="lead col-md-8">
|
||||||
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}&redirect_uri={{ githubRedirectUri }}/attach" class="btn btn-primary"><i class="fa fa-github fa-lg"></i> Connect with GitHub</a>
|
<i class="fa fa-github fa-lg" style="margin-right: 6px;" data-title="GitHub" bs-tooltip="tooltip.title"></i>
|
||||||
|
Account attached to Github Account
|
||||||
|
</div>
|
||||||
|
<div ng-show="!hasGithubLogin" class="col-md-4">
|
||||||
|
<span class="external-login-button" provider="github" action="attach"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Google -->
|
||||||
|
<div class="row" quay-show="cuser && Features.GOOGLE_LOGIN">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Google Login:</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div ng-show="hasGoogleLogin && googleLogin" class="lead col-md-8">
|
||||||
|
<i class="fa fa-google fa-lg" style="margin-right: 6px;" data-title="Google" bs-tooltip="tooltip.title"></i>
|
||||||
|
<b>{{ googleLogin }}</b>
|
||||||
|
</div>
|
||||||
|
<div ng-show="hasGoogleLogin && !googleLogin" class="lead col-md-8">
|
||||||
|
<i class="fa fa-google fa-lg" style="margin-right: 6px;" data-title="Google" bs-tooltip="tooltip.title"></i>
|
||||||
|
Account attached to Google Account
|
||||||
|
</div>
|
||||||
|
<div ng-show="!hasGoogleLogin" class="col-md-4">
|
||||||
|
<span class="external-login-button" provider="google" action="attach"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Robot accounts tab -->
|
<!-- Robot accounts tab -->
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
<title>Error Logging in with GitHub · Quay.io</title>
|
<title>Error Logging in with {{ service_name }} · Quay.io</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body_content %}
|
{% block body_content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2>There was an error logging in with GitHub.</h2>
|
<h2>There was an error logging in with {{ service_name }}.</h2>
|
||||||
|
|
||||||
{% if error_message %}
|
{% if error_message %}
|
||||||
<div class="alert alert-danger">{{ error_message }}</div>
|
<div class="alert alert-danger">{{ error_message }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Please register using the <a href="/">registration form</a> to continue.
|
Please register using the <a ng-href="{{ service_url }}/signin" target="_self">registration form</a> to continue.
|
||||||
You will be able to connect your github account to your Quay.io account
|
You will be able to connect your account to your Quay.io account
|
||||||
in the user settings.
|
in the user settings.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
Reference in a new issue