2013-12-27 22:19:14 +00:00
|
|
|
import logging
|
2014-02-26 00:39:43 +00:00
|
|
|
import json
|
2014-03-26 22:36:59 +00:00
|
|
|
import string
|
2014-09-04 18:24:20 +00:00
|
|
|
import datetime
|
2015-02-06 22:52:09 +00:00
|
|
|
import os
|
2015-12-28 18:27:32 +00:00
|
|
|
import re
|
2013-12-27 22:19:14 +00:00
|
|
|
|
2016-03-09 23:09:20 +00:00
|
|
|
from random import SystemRandom
|
|
|
|
from functools import wraps
|
2015-01-17 03:41:54 +00:00
|
|
|
|
2016-03-09 23:09:20 +00:00
|
|
|
from cachetools import lru_cache
|
2014-09-04 18:24:20 +00:00
|
|
|
from flask import make_response, render_template, request, abort, session
|
2016-09-29 00:17:14 +00:00
|
|
|
from flask_login import login_user
|
|
|
|
from flask_principal import identity_changed
|
|
|
|
|
|
|
|
import endpoints.decorated # Register the various exceptions via decorators.
|
|
|
|
import features
|
2013-12-27 22:19:14 +00:00
|
|
|
|
2016-10-24 19:37:25 +00:00
|
|
|
from app import app, oauth_apps, LoginWrappedDBUser, user_analytics, license_validator
|
2014-03-19 22:21:58 +00:00
|
|
|
from auth import scopes
|
2016-09-29 00:17:14 +00:00
|
|
|
from auth.permissions import QuayDeferredPermissionUser
|
2015-08-03 20:56:32 +00:00
|
|
|
from config import frontend_visible_config
|
2014-05-09 22:49:33 +00:00
|
|
|
from external_libraries import get_external_javascript, get_external_css
|
2016-01-21 20:40:51 +00:00
|
|
|
from util.names import parse_namespace_repository
|
2016-09-29 00:17:14 +00:00
|
|
|
from util.secscan import PRIORITY_LEVELS
|
2016-10-10 17:00:59 +00:00
|
|
|
from util.timedeltastring import convert_to_timedelta
|
2013-12-27 22:19:14 +00:00
|
|
|
|
2014-04-05 03:26:10 +00:00
|
|
|
|
2013-12-27 22:19:14 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
route_data = None
|
|
|
|
|
2015-02-06 22:52:09 +00:00
|
|
|
CACHE_BUSTERS_JSON = 'static/dist/cachebusters.json'
|
|
|
|
CACHE_BUSTERS = None
|
|
|
|
|
2016-09-29 00:17:14 +00:00
|
|
|
|
2015-02-06 22:52:09 +00:00
|
|
|
def get_cache_busters():
|
|
|
|
""" Retrieves the cache busters hashes. """
|
|
|
|
global CACHE_BUSTERS
|
|
|
|
if CACHE_BUSTERS is not None:
|
|
|
|
return CACHE_BUSTERS
|
|
|
|
|
|
|
|
if not os.path.exists(CACHE_BUSTERS_JSON):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
with open(CACHE_BUSTERS_JSON, 'r') as f:
|
|
|
|
CACHE_BUSTERS = json.loads(f.read())
|
|
|
|
return CACHE_BUSTERS
|
|
|
|
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
def parse_repository_name(include_tag=False,
|
|
|
|
ns_kwarg_name='namespace_name',
|
|
|
|
repo_kwarg_name='repo_name',
|
|
|
|
tag_kwarg_name='tag_name',
|
|
|
|
incoming_repo_kwarg='repository'):
|
|
|
|
def inner(func):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(*args, **kwargs):
|
2016-03-09 23:09:20 +00:00
|
|
|
repo_name_components = parse_namespace_repository(kwargs[incoming_repo_kwarg],
|
|
|
|
app.config['LIBRARY_NAMESPACE'],
|
|
|
|
include_tag=include_tag)
|
2016-03-09 21:20:28 +00:00
|
|
|
del kwargs[incoming_repo_kwarg]
|
2016-03-09 23:09:20 +00:00
|
|
|
kwargs[ns_kwarg_name] = repo_name_components[0]
|
|
|
|
kwargs[repo_kwarg_name] = repo_name_components[1]
|
2016-03-09 21:20:28 +00:00
|
|
|
if include_tag:
|
2016-03-09 23:09:20 +00:00
|
|
|
kwargs[tag_kwarg_name] = repo_name_components[2]
|
2016-03-09 21:20:28 +00:00
|
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
2014-04-03 23:32:09 +00:00
|
|
|
def route_show_if(value):
|
|
|
|
def decorator(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
if not value:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return decorated_function
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
def route_hide_if(value):
|
|
|
|
def decorator(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
if value:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return decorated_function
|
|
|
|
return decorator
|
|
|
|
|
2014-03-11 18:30:00 +00:00
|
|
|
|
2014-02-16 23:59:24 +00:00
|
|
|
def truthy_param(param):
|
|
|
|
return param not in {False, 'false', 'False', '0', 'FALSE', '', 'null'}
|
|
|
|
|
|
|
|
|
2015-05-18 16:38:39 +00:00
|
|
|
def param_required(param_name, allow_body=False):
|
2014-07-21 19:09:31 +00:00
|
|
|
def wrapper(wrapped):
|
|
|
|
@wraps(wrapped)
|
|
|
|
def decorated(*args, **kwargs):
|
|
|
|
if param_name not in request.args:
|
2015-07-20 21:04:06 +00:00
|
|
|
if not allow_body or param_name not in request.values:
|
2015-05-18 16:38:39 +00:00
|
|
|
abort(make_response('Required param: %s' % param_name, 400))
|
2014-07-21 19:09:31 +00:00
|
|
|
return wrapped(*args, **kwargs)
|
|
|
|
return decorated
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2016-10-10 17:00:59 +00:00
|
|
|
def common_login(db_user, permanent_session=True):
|
2015-01-17 03:41:54 +00:00
|
|
|
if login_user(LoginWrappedDBUser(db_user.uuid, db_user)):
|
2014-11-11 22:22:37 +00:00
|
|
|
logger.debug('Successfully signed in as: %s (%s)' % (db_user.username, db_user.uuid))
|
2015-05-07 19:04:12 +00:00
|
|
|
new_identity = QuayDeferredPermissionUser.for_user(db_user)
|
2013-12-27 22:19:14 +00:00
|
|
|
identity_changed.send(app, identity=new_identity)
|
2014-09-04 18:24:20 +00:00
|
|
|
session['login_time'] = datetime.datetime.now()
|
2016-10-13 17:48:35 +00:00
|
|
|
|
2016-10-10 17:00:59 +00:00
|
|
|
if permanent_session and features.PERMANENT_SESSIONS:
|
|
|
|
session_timeout_str = app.config.get('SESSION_TIMEOUT', '31d')
|
|
|
|
session.permanent = True
|
|
|
|
session.permanent_session_lifetime = convert_to_timedelta(session_timeout_str)
|
|
|
|
|
2016-10-13 17:48:35 +00:00
|
|
|
# Inform our user analytics that we have a new "lead"
|
2016-11-09 20:29:53 +00:00
|
|
|
user_analytics.create_lead(db_user.email, db_user.username, db_user.given_name,
|
|
|
|
db_user.family_name, db_user.company)
|
2013-12-27 22:19:14 +00:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
logger.debug('User could not be logged in, inactive?.')
|
|
|
|
return False
|
2013-12-28 19:07:44 +00:00
|
|
|
|
2014-03-26 22:36:59 +00:00
|
|
|
def random_string():
|
|
|
|
random = SystemRandom()
|
|
|
|
return ''.join([random.choice(string.ascii_uppercase + string.digits) for _ in range(8)])
|
|
|
|
|
2014-04-15 20:35:15 +00:00
|
|
|
def list_files(path, extension):
|
|
|
|
import os
|
|
|
|
def matches(f):
|
|
|
|
return os.path.splitext(f)[1] == '.' + extension
|
|
|
|
|
|
|
|
def join_path(dp, f):
|
|
|
|
# Remove the static/ prefix. It is added in the template.
|
|
|
|
return os.path.join(dp, f)[len('static/'):]
|
|
|
|
|
|
|
|
filepath = 'static/' + path
|
|
|
|
return [join_path(dp, f) for dp, dn, files in os.walk(filepath) for f in files if matches(f)]
|
2014-04-15 19:51:32 +00:00
|
|
|
|
2015-12-28 18:27:32 +00:00
|
|
|
@lru_cache(maxsize=1)
|
|
|
|
def _get_version_number():
|
|
|
|
try:
|
|
|
|
with open('CHANGELOG.md') as f:
|
|
|
|
return re.search('(v[0-9]+\.[0-9]+\.[0-9]+)', f.readline()).group(0)
|
|
|
|
except IOError:
|
|
|
|
return ''
|
|
|
|
|
2016-01-08 18:53:27 +00:00
|
|
|
def render_page_template(name, route_data=None, **kwargs):
|
2015-02-06 22:52:09 +00:00
|
|
|
debugging = app.config.get('DEBUGGING', False)
|
|
|
|
if debugging:
|
2014-04-15 20:35:15 +00:00
|
|
|
# If DEBUGGING is enabled, then we load the full set of individual JS and CSS files
|
|
|
|
# from the file system.
|
|
|
|
library_styles = list_files('lib', 'css')
|
|
|
|
main_styles = list_files('css', 'css')
|
|
|
|
library_scripts = list_files('lib', 'js')
|
|
|
|
main_scripts = list_files('js', 'js')
|
2014-04-16 18:23:22 +00:00
|
|
|
|
|
|
|
file_lists = [library_styles, main_styles, library_scripts, main_scripts]
|
|
|
|
for file_list in file_lists:
|
|
|
|
file_list.sort()
|
2014-04-15 20:35:15 +00:00
|
|
|
else:
|
|
|
|
library_styles = []
|
|
|
|
main_styles = ['dist/quay-frontend.css']
|
|
|
|
library_scripts = []
|
|
|
|
main_scripts = ['dist/quay-frontend.min.js']
|
2014-04-15 19:51:32 +00:00
|
|
|
|
2014-11-25 20:32:10 +00:00
|
|
|
use_cdn = app.config.get('USE_CDN', True)
|
|
|
|
if request.args.get('use_cdn') is not None:
|
|
|
|
use_cdn = request.args.get('use_cdn') == 'true'
|
|
|
|
|
|
|
|
external_styles = get_external_css(local=not use_cdn)
|
|
|
|
external_scripts = get_external_javascript(local=not use_cdn)
|
2014-05-09 22:49:33 +00:00
|
|
|
|
2016-04-01 18:10:11 +00:00
|
|
|
# Add Stripe checkout if billing is enabled.
|
|
|
|
if features.BILLING:
|
|
|
|
external_scripts.append('//checkout.stripe.com/checkout.js')
|
|
|
|
|
2015-02-06 22:52:09 +00:00
|
|
|
def add_cachebusters(filenames):
|
|
|
|
cachebusters = get_cache_busters()
|
|
|
|
for filename in filenames:
|
|
|
|
cache_buster = cachebusters.get(filename, random_string()) if not debugging else 'debugging'
|
|
|
|
yield (filename, cache_buster)
|
|
|
|
|
2014-11-05 21:43:37 +00:00
|
|
|
def get_oauth_config():
|
|
|
|
oauth_config = {}
|
|
|
|
for oauth_app in oauth_apps:
|
2014-11-07 01:35:52 +00:00
|
|
|
oauth_config[oauth_app.key_name] = oauth_app.get_public_config()
|
2014-11-05 21:43:37 +00:00
|
|
|
|
|
|
|
return oauth_config
|
|
|
|
|
2014-10-22 18:49:33 +00:00
|
|
|
contact_href = None
|
|
|
|
if len(app.config.get('CONTACT_INFO', [])) == 1:
|
|
|
|
contact_href = app.config['CONTACT_INFO'][0]
|
|
|
|
|
2015-12-28 18:27:32 +00:00
|
|
|
version_number = ''
|
|
|
|
if not features.BILLING:
|
|
|
|
version_number = ' - ' + _get_version_number()
|
|
|
|
|
2015-02-06 22:52:09 +00:00
|
|
|
resp = make_response(render_template(name,
|
2016-01-08 18:53:27 +00:00
|
|
|
route_data=json.dumps(route_data),
|
2014-05-09 22:49:33 +00:00
|
|
|
external_styles=external_styles,
|
|
|
|
external_scripts=external_scripts,
|
2015-02-06 22:52:09 +00:00
|
|
|
main_styles=add_cachebusters(main_styles),
|
|
|
|
library_styles=add_cachebusters(library_styles),
|
|
|
|
main_scripts=add_cachebusters(main_scripts),
|
|
|
|
library_scripts=add_cachebusters(library_scripts),
|
2014-04-05 03:26:10 +00:00
|
|
|
feature_set=json.dumps(features.get_features()),
|
2015-08-03 20:56:32 +00:00
|
|
|
config_set=json.dumps(frontend_visible_config(app.config)),
|
2014-11-05 21:43:37 +00:00
|
|
|
oauth_set=json.dumps(get_oauth_config()),
|
2015-07-15 22:13:15 +00:00
|
|
|
scope_set=json.dumps(scopes.app_scopes(app.config)),
|
2015-11-10 20:08:14 +00:00
|
|
|
vuln_priority_set=json.dumps(PRIORITY_LEVELS),
|
2016-06-16 20:27:18 +00:00
|
|
|
enterprise_logo=app.config.get('ENTERPRISE_LOGO_URL', ''),
|
2014-04-08 23:14:24 +00:00
|
|
|
mixpanel_key=app.config.get('MIXPANEL_KEY', ''),
|
2016-10-13 17:48:35 +00:00
|
|
|
munchkin_key=app.config.get('MARKETO_MUNCHKIN_ID', ''),
|
2016-04-06 20:15:26 +00:00
|
|
|
google_tagmanager_key=app.config.get('GOOGLE_TAGMANAGER_KEY', ''),
|
2016-08-16 18:35:39 +00:00
|
|
|
google_anaytics_key=app.config.get('GOOGLE_ANALYTICS_KEY', ''),
|
2014-04-28 22:59:22 +00:00
|
|
|
sentry_public_dsn=app.config.get('SENTRY_PUBLIC_DSN', ''),
|
2014-04-08 23:14:24 +00:00
|
|
|
is_debug=str(app.config.get('DEBUGGING', False)).lower(),
|
2016-08-08 22:18:35 +00:00
|
|
|
show_chat=features.SUPPORT_CHAT,
|
2016-02-16 20:31:23 +00:00
|
|
|
aci_conversion=features.ACI_CONVERSION,
|
2014-05-28 19:22:36 +00:00
|
|
|
has_billing=features.BILLING,
|
2014-10-22 18:49:33 +00:00
|
|
|
contact_href=contact_href,
|
2015-01-13 23:00:01 +00:00
|
|
|
hostname=app.config['SERVER_HOSTNAME'],
|
|
|
|
preferred_scheme=app.config['PREFERRED_URL_SCHEME'],
|
2015-12-28 18:27:32 +00:00
|
|
|
version_number=version_number,
|
2016-10-24 19:37:25 +00:00
|
|
|
license_insufficient=license_validator.insufficient,
|
2016-11-07 19:07:39 +00:00
|
|
|
license_expiring=license_validator.expiring_soon,
|
2015-12-28 18:27:32 +00:00
|
|
|
**kwargs))
|
2014-04-05 03:26:10 +00:00
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
resp.headers['X-FRAME-OPTIONS'] = 'DENY'
|
2014-02-21 19:52:40 +00:00
|
|
|
return resp
|
2014-02-26 00:39:43 +00:00
|
|
|
|