import logging from flask import (abort, redirect, request, url_for, make_response, Response, Blueprint, send_from_directory, jsonify) from avatar_generator import Avatar from flask.ext.login import current_user from urlparse import urlparse from health.healthcheck import HealthCheck from data import model from data.model.oauth import DatabaseAuthorizationProvider from app import app, billing as stripe, build_logs, avatar from auth.auth import require_session_login, process_oauth from auth.permissions import AdministerOrganizationPermission, ReadRepositoryPermission from util.invoice import renderInvoiceToPdf from util.seo import render_snapshot from util.cache import no_cache from endpoints.common import common_login, render_page_template, route_show_if, param_required from endpoints.csrf import csrf_protect, generate_csrf_token from endpoints.registry import set_cache_headers from util.names import parse_repository_name, parse_repository_name_and_tag from util.useremails import send_email_changed from auth import scopes import features logger = logging.getLogger(__name__) web = Blueprint('web', __name__) STATUS_TAGS = app.config['STATUS_TAGS'] @web.route('/', methods=['GET'], defaults={'path': ''}) @web.route('/organization/', methods=['GET']) @no_cache def index(path, **kwargs): return render_page_template('index.html', **kwargs) @web.route('/500', methods=['GET']) def internal_error_display(): return render_page_template('500.html') @web.route('/snapshot', methods=['GET']) @web.route('/snapshot/', methods=['GET']) @web.route('/snapshot/', methods=['GET']) def snapshot(path = ''): parsed = urlparse(request.url) final_url = '%s://%s/%s' % (parsed.scheme, 'localhost', path) result = render_snapshot(final_url) if result: return result abort(404) @web.route('/plans/') @no_cache @route_show_if(features.BILLING) def plans(): return index('') @web.route('/guide/') @no_cache def guide(): return index('') @web.route('/tour/') @web.route('/tour/') @no_cache def tour(path = ''): return index('') @web.route('/tutorial/') @no_cache def tutorial(): return index('') @web.route('/organizations/') @web.route('/organizations/new/') @no_cache def organizations(): return index('') @web.route('/user/') @no_cache def user(): return index('') @web.route('/superuser/') @no_cache @route_show_if(features.SUPER_USERS) def superuser(): return index('') @web.route('/signin/') @no_cache def signin(redirect=None): return index('') @web.route('/contact/') @no_cache def contact(): return index('') @web.route('/about/') @no_cache def about(): return index('') @web.route('/new/') @no_cache def new(): return index('') @web.route('/confirminvite') @no_cache def confirm_invite(): code = request.values['code'] return index('', code=code) @web.route('/repository/', defaults={'path': ''}) @web.route('/repository/', methods=['GET']) @no_cache def repository(path): return index('') @web.route('/security/') @no_cache def security(): return index('') @web.route('/v1') @web.route('/v1/') @no_cache def v1(): return index('') @web.route('/health', methods=['GET']) @no_cache def health(): client = app.config['HTTPCLIENT'] db_healthy = model.check_health() buildlogs_healthy = build_logs.check_health() hostname_parts = app.config['SERVER_HOSTNAME'].split(':') port = '' if len(hostname_parts) == 2: port = ':' + hostname_parts[1] registry_url = '%s://localhost%s/v1/_internal_ping' % (app.config['PREFERRED_URL_SCHEME'], port) registry_healthy = client.get(registry_url, verify=False).status_code == 200 check = HealthCheck.get_check(app.config['HEALTH_CHECKER'][0], app.config['HEALTH_CHECKER'][1]) (data, is_healthy) = check.conduct_healthcheck(db_healthy, buildlogs_healthy, registry_healthy) response = jsonify(dict(data=data, is_healthy=is_healthy)) response.status_code = 200 if is_healthy else 503 return response @web.route('/status', methods=['GET']) @no_cache def status(): db_healthy = model.check_health() buildlogs_healthy = build_logs.check_health() response = jsonify({ 'db_healthy': db_healthy, 'buildlogs_healthy': buildlogs_healthy, 'is_testing': app.config['TESTING'], }) response.status_code = 200 if db_healthy and buildlogs_healthy else 503 return response @app.route("/avatar/") @set_cache_headers def render_avatar(avatar_hash, headers): try: size = int(request.args.get('size', 16)) except ValueError: size = 16 generated = Avatar.generate(size, avatar_hash, "PNG") resp = make_response(generated, 200, {'Content-Type': 'image/png'}) resp.headers.extend(headers) return resp @web.route('/tos', methods=['GET']) @no_cache def tos(): return render_page_template('tos.html') @web.route('/disclaimer', methods=['GET']) @no_cache def disclaimer(): return render_page_template('disclaimer.html') @web.route('/privacy', methods=['GET']) @no_cache def privacy(): return render_page_template('privacy.html') @web.route('/robots.txt', methods=['GET']) @no_cache def robots(): return send_from_directory('static', 'robots.txt') @web.route('/') @no_cache @process_oauth @parse_repository_name_and_tag def redirect_to_repository(namespace, reponame, tag): permission = ReadRepositoryPermission(namespace, reponame) is_public = model.repository_is_public(namespace, reponame) if permission.can() or is_public: repository_name = '/'.join([namespace, reponame]) return redirect(url_for('web.repository', path=repository_name, tag=tag)) abort(404) @web.route('/receipt', methods=['GET']) @route_show_if(features.BILLING) @require_session_login def receipt(): if not current_user.is_authenticated(): abort(401) return invoice_id = request.args.get('id') if invoice_id: invoice = stripe.Invoice.retrieve(invoice_id) if invoice: 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"}) abort(404) @web.route('/authrepoemail', methods=['GET']) @route_show_if(features.MAILING) def confirm_repo_email(): code = request.values['code'] record = None try: record = model.confirm_email_authorization_for_repo(code) except model.DataModelException as ex: return render_page_template('confirmerror.html', error_message=ex.message) message = """ Your E-mail address has been authorized to receive notifications for repository %s/%s. """ % (app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'], record.repository.namespace_user.username, record.repository.name, record.repository.namespace_user.username, record.repository.name) return render_page_template('message.html', message=message) @web.route('/confirm', methods=['GET']) @route_show_if(features.MAILING) def confirm_email(): code = request.values['code'] user = None new_email = None try: user, new_email, old_email = model.confirm_user_email(code) except model.DataModelException as ex: return render_page_template('confirmerror.html', error_message=ex.message) if new_email: send_email_changed(user.username, old_email, new_email) common_login(user) return redirect(url_for('web.user', tab='email') if new_email else url_for('web.index')) @web.route('/recovery', methods=['GET']) def confirm_recovery(): code = request.values['code'] user = model.validate_reset_code(code) if user: common_login(user) return redirect(url_for('web.user')) else: abort(403) @web.route('/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. tags = model.list_repository_tags(namespace, repository) is_empty = len(list(tags)) == 0 build = model.get_recent_repository_build(namespace, repository) if not is_empty and (not build or build.phase == 'complete'): status_name = 'ready' elif build and build.phase == 'error': status_name = 'failed' elif build and build.phase != 'complete': status_name = 'building' else: status_name = 'none' response = make_response(STATUS_TAGS[status_name]) response.content_type = 'image/svg+xml' return response 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) @web.route('/oauth/authorizeapp', methods=['POST']) @csrf_protect 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) @web.route('/oauth/denyapp', methods=['POST']) @csrf_protect 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) @web.route('/oauth/authorize', methods=['GET']) @no_cache @param_required('client_id') @param_required('redirect_uri') @param_required('scope') 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) if (not current_user.is_authenticated() or not provider.validate_has_scopes(client_id, current_user.db_user().username, scope)): if redirect_uri != 'display' and not provider.validate_redirect_uri(client_id, redirect_uri): current_app = provider.get_application_for_client_id(client_id) if not current_app: abort(404) return provider._make_redirect_error_response(current_app.redirect_uri, 'redirect_uri_mismatch') # Load the scope information. scope_info = scopes.get_scope_information(scope) if not scope_info: abort(404) return # 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, 'avatar': avatar.compute_hash(oauth_app.avatar_email, name=oauth_app.name), 'organization': { 'name': oauth_app.organization.username, 'avatar': avatar.compute_hash(oauth_app.organization.email, name=oauth_app.organization.username) } } # Show the authorization page. has_dangerous_scopes = any([check_scope['dangerous'] for check_scope in scope_info]) return render_page_template('oauthorize.html', scopes=scope_info, has_dangerous_scopes=has_dangerous_scopes, application=oauth_app_view, enumerate=enumerate, client_id=client_id, redirect_uri=redirect_uri, scope=scope, csrf_token_val=generate_csrf_token()) 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) @web.route('/oauth/access_token', methods=['POST']) @no_cache @param_required('grant_type') @param_required('client_id') @param_required('client_secret') @param_required('redirect_uri') @param_required('code') @param_required('scope') 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)