2013-09-25 20:46:28 +00:00
|
|
|
import logging
|
2014-02-28 21:23:36 +00:00
|
|
|
import os
|
2013-09-25 20:46:28 +00:00
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
from flask import (abort, redirect, request, url_for, make_response, Response,
|
2014-05-13 22:53:42 +00:00
|
|
|
Blueprint, send_from_directory, jsonify)
|
2014-02-18 20:50:15 +00:00
|
|
|
from flask.ext.login import current_user
|
2013-11-19 00:17:58 +00:00
|
|
|
from urlparse import urlparse
|
2013-09-25 16:45:12 +00:00
|
|
|
|
|
|
|
from data import model
|
2014-03-12 16:37:06 +00:00
|
|
|
from data.model.oauth import DatabaseAuthorizationProvider
|
2014-05-13 22:53:42 +00:00
|
|
|
from app import app, billing as stripe, build_logs
|
2014-04-04 07:01:48 +00:00
|
|
|
from auth.auth import require_session_login
|
2013-12-27 23:01:44 +00:00
|
|
|
from auth.permissions import AdministerOrganizationPermission
|
2013-11-18 19:49:54 +00:00
|
|
|
from util.invoice import renderInvoiceToPdf
|
2013-11-18 23:42:27 +00:00
|
|
|
from util.seo import render_snapshot
|
2014-01-02 23:01:34 +00:00
|
|
|
from util.cache import no_cache
|
2014-04-03 23:32:09 +00:00
|
|
|
from endpoints.common import common_login, render_page_template, route_show_if, route_hide_if
|
2014-03-25 18:32:26 +00:00
|
|
|
from endpoints.csrf import csrf_protect, generate_csrf_token
|
2014-02-28 21:23:36 +00:00
|
|
|
from util.names import parse_repository_name
|
2014-03-14 22:57:28 +00:00
|
|
|
from util.gravatar import compute_hash
|
|
|
|
from auth import scopes
|
2013-11-19 00:17:58 +00:00
|
|
|
|
2014-04-06 04:36:19 +00:00
|
|
|
import features
|
|
|
|
|
2013-09-25 20:46:28 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
web = Blueprint('web', __name__)
|
|
|
|
|
2014-03-05 19:35:11 +00:00
|
|
|
STATUS_TAGS = app.config['STATUS_TAGS']
|
2013-09-25 20:46:28 +00:00
|
|
|
|
2014-03-10 21:01:36 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/', methods=['GET'], defaults={'path': ''})
|
|
|
|
@web.route('/organization/<path:path>', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-10 23:06:04 +00:00
|
|
|
def index(path):
|
2013-12-27 22:19:14 +00:00
|
|
|
return render_page_template('index.html')
|
2013-09-25 16:45:12 +00:00
|
|
|
|
|
|
|
|
2014-03-10 21:01:36 +00:00
|
|
|
@web.route('/500', methods=['GET'])
|
|
|
|
def internal_error_display():
|
|
|
|
return render_page_template('500.html')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/snapshot', methods=['GET'])
|
|
|
|
@web.route('/snapshot/', methods=['GET'])
|
|
|
|
@web.route('/snapshot/<path:path>', methods=['GET'])
|
2013-11-18 22:11:06 +00:00
|
|
|
def snapshot(path = ''):
|
2013-11-19 00:17:58 +00:00
|
|
|
parsed = urlparse(request.url)
|
|
|
|
final_url = '%s://%s/%s' % (parsed.scheme, 'localhost', path)
|
|
|
|
result = render_snapshot(final_url)
|
2013-11-18 22:11:06 +00:00
|
|
|
if result:
|
|
|
|
return result
|
|
|
|
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/plans/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2014-04-06 04:36:19 +00:00
|
|
|
@route_show_if(features.BILLING)
|
2013-10-14 02:06:31 +00:00
|
|
|
def plans():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/guide/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-14 02:06:31 +00:00
|
|
|
def guide():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2014-02-06 02:00:04 +00:00
|
|
|
@web.route('/tutorial/')
|
|
|
|
@no_cache
|
|
|
|
def tutorial():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/organizations/')
|
|
|
|
@web.route('/organizations/new/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-11-07 06:48:58 +00:00
|
|
|
def organizations():
|
|
|
|
return index('')
|
|
|
|
|
2013-12-27 23:01:44 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/user/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-14 02:06:31 +00:00
|
|
|
def user():
|
|
|
|
return index('')
|
|
|
|
|
2014-04-10 04:26:55 +00:00
|
|
|
@web.route('/superuser/')
|
|
|
|
@no_cache
|
|
|
|
@route_show_if(features.SUPER_USERS)
|
|
|
|
def superuser():
|
|
|
|
return index('')
|
|
|
|
|
2013-10-14 02:06:31 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/signin/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-14 21:50:07 +00:00
|
|
|
def signin():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2014-01-23 19:51:43 +00:00
|
|
|
@web.route('/contact/')
|
|
|
|
@no_cache
|
2013-12-17 22:02:37 +00:00
|
|
|
def contact():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2014-02-07 00:20:19 +00:00
|
|
|
@web.route('/about/')
|
|
|
|
@no_cache
|
|
|
|
def about():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/new/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-24 21:41:55 +00:00
|
|
|
def new():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2014-02-18 20:50:15 +00:00
|
|
|
@web.route('/repository/', defaults={'path': ''})
|
|
|
|
@web.route('/repository/<path:path>', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2014-02-18 20:50:15 +00:00
|
|
|
def repository(path):
|
2013-10-16 01:50:14 +00:00
|
|
|
return index('')
|
|
|
|
|
2013-11-22 20:54:23 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/security/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-11-22 20:54:23 +00:00
|
|
|
def security():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/v1')
|
|
|
|
@web.route('/v1/')
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-17 21:45:08 +00:00
|
|
|
def v1():
|
|
|
|
return index('')
|
2013-10-16 01:50:14 +00:00
|
|
|
|
2013-11-22 20:54:23 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/status', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-02 18:35:21 +00:00
|
|
|
def status():
|
2014-05-13 22:53:42 +00:00
|
|
|
db_healthy = model.check_health()
|
|
|
|
buildlogs_healthy = build_logs.check_health()
|
|
|
|
|
|
|
|
response = jsonify({
|
|
|
|
'db_healthy': db_healthy,
|
|
|
|
'buildlogs_healthy': buildlogs_healthy,
|
|
|
|
})
|
|
|
|
response.status_code = 200 if db_healthy and buildlogs_healthy else 503
|
|
|
|
|
|
|
|
return response
|
2013-10-02 18:35:21 +00:00
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/tos', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-01 21:44:13 +00:00
|
|
|
def tos():
|
2013-12-27 22:19:14 +00:00
|
|
|
return render_page_template('tos.html')
|
2013-10-01 21:44:13 +00:00
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/disclaimer', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-11-22 17:32:05 +00:00
|
|
|
def disclaimer():
|
2013-12-27 22:19:14 +00:00
|
|
|
return render_page_template('disclaimer.html')
|
2013-11-22 17:32:05 +00:00
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/privacy', methods=['GET'])
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2013-10-01 21:44:13 +00:00
|
|
|
def privacy():
|
2013-12-27 22:19:14 +00:00
|
|
|
return render_page_template('privacy.html')
|
2013-10-01 21:44:13 +00:00
|
|
|
|
|
|
|
|
2014-04-21 23:46:00 +00:00
|
|
|
@web.route('/robots.txt', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def robots():
|
|
|
|
return send_from_directory('static', 'robots.txt')
|
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/receipt', methods=['GET'])
|
2014-04-06 04:36:19 +00:00
|
|
|
@route_show_if(features.BILLING)
|
2014-04-04 07:01:48 +00:00
|
|
|
@require_session_login
|
2013-11-18 19:49:54 +00:00
|
|
|
def receipt():
|
2013-12-21 03:38:53 +00:00
|
|
|
if not current_user.is_authenticated():
|
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
2013-11-18 19:49:54 +00:00
|
|
|
id = request.args.get('id')
|
|
|
|
if id:
|
|
|
|
invoice = stripe.Invoice.retrieve(id)
|
|
|
|
if invoice:
|
2013-12-21 03:38:53 +00:00
|
|
|
user_or_org = model.get_user_or_org_by_customer_id(invoice.customer)
|
|
|
|
|
|
|
|
if user_or_org:
|
|
|
|
if user_or_org.organization:
|
|
|
|
admin_org = AdministerOrganizationPermission(user_or_org.username)
|
|
|
|
if not admin_org.can():
|
|
|
|
abort(404)
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
if not user_or_org.username == current_user.db_user().username:
|
|
|
|
abort(404)
|
|
|
|
return
|
|
|
|
|
|
|
|
file_data = renderInvoiceToPdf(invoice, user_or_org)
|
|
|
|
return Response(file_data,
|
|
|
|
mimetype="application/pdf",
|
|
|
|
headers={"Content-Disposition": "attachment;filename=receipt.pdf"})
|
2013-11-18 19:49:54 +00:00
|
|
|
abort(404)
|
|
|
|
|
2013-09-27 23:55:04 +00:00
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/confirm', methods=['GET'])
|
2013-09-27 23:29:01 +00:00
|
|
|
def confirm_email():
|
2013-09-27 23:55:04 +00:00
|
|
|
code = request.values['code']
|
2014-01-17 22:23:52 +00:00
|
|
|
user = None
|
|
|
|
new_email = None
|
2013-12-19 00:47:42 +00:00
|
|
|
|
|
|
|
try:
|
2014-01-17 22:23:52 +00:00
|
|
|
user, new_email = model.confirm_user_email(code)
|
2013-12-19 00:47:42 +00:00
|
|
|
except model.DataModelException as ex:
|
2014-01-17 22:23:52 +00:00
|
|
|
return render_page_template('confirmerror.html', error_message=ex.message)
|
2014-01-17 22:20:51 +00:00
|
|
|
|
2014-01-17 22:23:52 +00:00
|
|
|
common_login(user)
|
2013-09-27 23:55:04 +00:00
|
|
|
|
2014-01-30 22:23:14 +00:00
|
|
|
return redirect(url_for('web.user', tab='email')
|
|
|
|
if new_email else url_for('web.index'))
|
2013-09-27 23:29:01 +00:00
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/recovery', methods=['GET'])
|
2013-10-14 21:50:07 +00:00
|
|
|
def confirm_recovery():
|
|
|
|
code = request.values['code']
|
|
|
|
user = model.validate_reset_code(code)
|
2013-09-27 23:29:01 +00:00
|
|
|
|
2013-10-14 21:50:07 +00:00
|
|
|
if user:
|
|
|
|
common_login(user)
|
2014-01-30 22:23:14 +00:00
|
|
|
return redirect(url_for('web.user'))
|
2013-10-14 21:50:07 +00:00
|
|
|
else:
|
|
|
|
abort(403)
|
2014-02-28 21:23:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
@web.route('/repository/<path:repository>/status', methods=['GET'])
|
|
|
|
@parse_repository_name
|
|
|
|
@no_cache
|
|
|
|
def build_status_badge(namespace, repository):
|
|
|
|
token = request.args.get('token', None)
|
|
|
|
is_public = model.repository_is_public(namespace, repository)
|
|
|
|
if not is_public:
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if not repo or token != repo.badge_token:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
# Lookup the tags for the repository.
|
2014-03-05 19:57:14 +00:00
|
|
|
tags = model.list_repository_tags(namespace, repository)
|
|
|
|
is_empty = len(list(tags)) == 0
|
2014-02-28 21:23:36 +00:00
|
|
|
build = model.get_recent_repository_build(namespace, repository)
|
2014-03-05 19:57:14 +00:00
|
|
|
|
|
|
|
if not is_empty and (not build or build.phase == 'complete'):
|
|
|
|
status_name = 'ready'
|
|
|
|
elif build and build.phase == 'error':
|
|
|
|
status_name = 'failed'
|
2014-03-05 20:14:12 +00:00
|
|
|
elif build and build.phase != 'complete':
|
2014-03-05 19:57:14 +00:00
|
|
|
status_name = 'building'
|
2014-02-28 21:23:36 +00:00
|
|
|
else:
|
2014-03-05 19:57:14 +00:00
|
|
|
status_name = 'none'
|
2014-02-28 21:23:36 +00:00
|
|
|
|
2014-03-05 19:57:14 +00:00
|
|
|
response = make_response(STATUS_TAGS[status_name])
|
2014-02-28 21:23:36 +00:00
|
|
|
response.content_type = 'image/svg+xml'
|
|
|
|
return response
|
2014-03-12 16:37:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FlaskAuthorizationProvider(DatabaseAuthorizationProvider):
|
|
|
|
def get_authorized_user(self):
|
|
|
|
return current_user.db_user()
|
|
|
|
|
|
|
|
def _make_response(self, body='', headers=None, status_code=200):
|
|
|
|
return make_response(body, status_code, headers)
|
|
|
|
|
|
|
|
|
2014-03-14 22:57:28 +00:00
|
|
|
@web.route('/oauth/authorizeapp', methods=['POST'])
|
2014-03-25 18:32:26 +00:00
|
|
|
@csrf_protect
|
2014-03-14 22:57:28 +00:00
|
|
|
def authorize_application():
|
|
|
|
if not current_user.is_authenticated():
|
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
|
|
|
provider = FlaskAuthorizationProvider()
|
|
|
|
client_id = request.form.get('client_id', None)
|
|
|
|
redirect_uri = request.form.get('redirect_uri', None)
|
|
|
|
scope = request.form.get('scope', None)
|
|
|
|
|
|
|
|
# Add the access token.
|
|
|
|
return provider.get_token_response('token', client_id, redirect_uri, scope=scope)
|
|
|
|
|
|
|
|
|
2014-03-24 22:30:22 +00:00
|
|
|
@web.route('/oauth/denyapp', methods=['POST'])
|
2014-03-25 18:32:26 +00:00
|
|
|
@csrf_protect
|
2014-03-24 22:30:22 +00:00
|
|
|
def deny_application():
|
|
|
|
if not current_user.is_authenticated():
|
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
|
|
|
provider = FlaskAuthorizationProvider()
|
|
|
|
client_id = request.form.get('client_id', None)
|
|
|
|
redirect_uri = request.form.get('redirect_uri', None)
|
|
|
|
scope = request.form.get('scope', None)
|
|
|
|
|
|
|
|
# Add the access token.
|
|
|
|
return provider.get_auth_denied_response('token', client_id, redirect_uri, scope=scope)
|
|
|
|
|
|
|
|
|
2014-03-12 16:37:06 +00:00
|
|
|
@web.route('/oauth/authorize', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def request_authorization_code():
|
|
|
|
provider = FlaskAuthorizationProvider()
|
|
|
|
response_type = request.args.get('response_type', 'code')
|
|
|
|
client_id = request.args.get('client_id', None)
|
|
|
|
redirect_uri = request.args.get('redirect_uri', None)
|
|
|
|
scope = request.args.get('scope', None)
|
|
|
|
|
2014-03-19 22:09:09 +00:00
|
|
|
if (not current_user.is_authenticated() or
|
|
|
|
not provider.validate_has_scopes(client_id, current_user.db_user().username, scope)):
|
2014-03-14 22:57:28 +00:00
|
|
|
if not provider.validate_redirect_uri(client_id, redirect_uri):
|
2014-03-26 20:45:11 +00:00
|
|
|
current_app = provider.get_application_for_client_id(client_id)
|
|
|
|
if not current_app:
|
|
|
|
abort(404)
|
|
|
|
|
2014-04-21 23:46:00 +00:00
|
|
|
return provider._make_redirect_error_response(current_app.redirect_uri,
|
|
|
|
'redirect_uri_mismatch')
|
2014-03-14 22:57:28 +00:00
|
|
|
|
|
|
|
# Load the scope information.
|
|
|
|
scope_info = scopes.get_scope_information(scope)
|
2014-03-18 21:05:27 +00:00
|
|
|
if not scope_info:
|
|
|
|
abort(404)
|
|
|
|
return
|
2014-03-14 22:57:28 +00:00
|
|
|
|
|
|
|
# Load the application information.
|
|
|
|
oauth_app = provider.get_application_for_client_id(client_id)
|
|
|
|
oauth_app_view = {
|
|
|
|
'name': oauth_app.name,
|
|
|
|
'description': oauth_app.description,
|
|
|
|
'url': oauth_app.application_uri,
|
|
|
|
'organization': {
|
|
|
|
'name': oauth_app.organization.username,
|
|
|
|
'gravatar': compute_hash(oauth_app.organization.email)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Show the authorization page.
|
|
|
|
return render_page_template('oauthorize.html', scopes=scope_info, application=oauth_app_view,
|
2014-04-21 23:46:00 +00:00
|
|
|
enumerate=enumerate, client_id=client_id,
|
|
|
|
redirect_uri=redirect_uri, scope=scope,
|
|
|
|
csrf_token_val=generate_csrf_token())
|
2014-03-14 22:57:28 +00:00
|
|
|
|
2014-03-12 16:37:06 +00:00
|
|
|
if response_type == 'token':
|
|
|
|
return provider.get_token_response(response_type, client_id, redirect_uri, scope=scope)
|
|
|
|
else:
|
|
|
|
return provider.get_authorization_code(response_type, client_id, redirect_uri, scope=scope)
|
2014-03-25 16:42:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
@web.route('/oauth/access_token', methods=['POST'])
|
|
|
|
@no_cache
|
|
|
|
def exchange_code_for_token():
|
|
|
|
grant_type = request.form.get('grant_type', None)
|
|
|
|
client_id = request.form.get('client_id', None)
|
|
|
|
client_secret = request.form.get('client_secret', None)
|
|
|
|
redirect_uri = request.form.get('redirect_uri', None)
|
|
|
|
code = request.form.get('code', None)
|
|
|
|
scope = request.form.get('scope', None)
|
|
|
|
|
|
|
|
provider = FlaskAuthorizationProvider()
|
|
|
|
return provider.get_token(grant_type, client_id, client_secret, redirect_uri, code, scope=scope)
|