2017-02-01 23:17:25 +00:00
|
|
|
import os
|
2016-01-08 00:07:23 +00:00
|
|
|
import json
|
2013-09-25 20:46:28 +00:00
|
|
|
import logging
|
|
|
|
|
2016-11-21 20:35:56 +00:00
|
|
|
from datetime import timedelta, datetime
|
2016-04-11 22:22:47 +00:00
|
|
|
|
2016-04-12 21:59:11 +00:00
|
|
|
from cachetools import lru_cache
|
2016-06-17 17:52:27 +00:00
|
|
|
from flask import (abort, redirect, request, url_for, make_response, Response, render_template,
|
2017-05-24 22:26:22 +00:00
|
|
|
Blueprint, jsonify, send_file, session)
|
2016-09-29 00:17:14 +00:00
|
|
|
from flask_login import current_user
|
2013-09-25 16:45:12 +00:00
|
|
|
|
2016-01-08 00:07:23 +00:00
|
|
|
import features
|
|
|
|
|
2016-06-17 17:52:27 +00:00
|
|
|
from app import (app, billing as stripe, build_logs, avatar, signer, log_archive, config_provider,
|
2018-11-27 16:28:32 +00:00
|
|
|
get_app_url, instance_keys, user_analytics, storage)
|
2016-01-08 00:07:23 +00:00
|
|
|
from auth import scopes
|
2016-09-29 19:24:57 +00:00
|
|
|
from auth.auth_context import get_authenticated_user
|
2017-03-16 21:05:26 +00:00
|
|
|
from auth.basic import has_basic_auth
|
|
|
|
from auth.decorators import require_session_login, process_oauth, process_auth_or_cookie
|
2014-12-23 19:01:00 +00:00
|
|
|
from auth.permissions import (AdministerOrganizationPermission, ReadRepositoryPermission,
|
2015-05-26 21:22:30 +00:00
|
|
|
SuperUserPermission, AdministerRepositoryPermission,
|
2016-09-21 17:53:09 +00:00
|
|
|
ModifyRepositoryPermission, OrganizationMemberPermission)
|
2016-01-08 00:07:23 +00:00
|
|
|
from buildtrigger.basehandler import BuildTriggerHandler
|
2015-09-11 21:40:32 +00:00
|
|
|
from buildtrigger.bitbuckethandler import BitbucketBuildTrigger
|
2016-01-08 00:07:23 +00:00
|
|
|
from buildtrigger.customhandler import CustomBuildTrigger
|
2015-09-11 21:40:32 +00:00
|
|
|
from buildtrigger.triggerutil import TriggerProviderException
|
2016-01-08 00:07:23 +00:00
|
|
|
from data import model
|
|
|
|
from data.database import db
|
2016-01-08 18:53:27 +00:00
|
|
|
from endpoints.api.discovery import swagger_route_data
|
2017-07-20 15:41:19 +00:00
|
|
|
from endpoints.common import common_login, render_page_template
|
2016-01-08 00:07:23 +00:00
|
|
|
from endpoints.csrf import csrf_protect, generate_csrf_token, verify_csrf
|
2017-07-20 15:41:19 +00:00
|
|
|
from endpoints.decorators import (anon_protect, anon_allowed, route_show_if, parse_repository_name,
|
|
|
|
param_required)
|
2016-01-08 00:07:23 +00:00
|
|
|
from health.healthcheck import get_healthchecker
|
2016-04-11 22:22:47 +00:00
|
|
|
from util.cache import no_cache
|
2016-01-08 00:07:23 +00:00
|
|
|
from util.headers import parse_basic_auth
|
|
|
|
from util.invoice import renderInvoiceToPdf
|
2017-04-05 15:25:29 +00:00
|
|
|
from util.saas.useranalytics import build_error_callback
|
2014-12-23 19:01:00 +00:00
|
|
|
from util.systemlogs import build_logs_archive
|
2016-01-08 00:07:23 +00:00
|
|
|
from util.useremails import send_email_changed
|
2017-04-24 19:59:10 +00:00
|
|
|
from util.registry.gzipinputstream import GzipInputStream
|
2017-02-01 23:17:25 +00:00
|
|
|
from _init import ROOT_DIR
|
|
|
|
|
2013-09-25 20:46:28 +00:00
|
|
|
|
2017-04-05 03:28:49 +00:00
|
|
|
PGP_KEY_MIMETYPE = 'application/pgp-keys'
|
|
|
|
|
|
|
|
|
2016-01-08 21:22:17 +00:00
|
|
|
@lru_cache(maxsize=1)
|
2016-01-08 18:53:27 +00:00
|
|
|
def _get_route_data():
|
2016-01-08 21:43:15 +00:00
|
|
|
return swagger_route_data(include_internal=True, compact=True)
|
2016-01-08 18:53:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
def render_page_template_with_routedata(name, *args, **kwargs):
|
|
|
|
return render_page_template(name, _get_route_data(), *args, **kwargs)
|
|
|
|
|
2015-01-17 03:41:54 +00:00
|
|
|
# Capture the unverified SSL errors.
|
2016-01-08 00:07:23 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2015-01-17 03:41:54 +00:00
|
|
|
logging.captureWarnings(True)
|
|
|
|
|
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': ''})
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2014-08-18 21:24:00 +00:00
|
|
|
def index(path, **kwargs):
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata('index.html', **kwargs)
|
2013-09-25 16:45:12 +00:00
|
|
|
|
2018-01-12 21:07:10 +00:00
|
|
|
@web.route('/_internal_ping')
|
|
|
|
@anon_allowed
|
|
|
|
def internal_ping():
|
|
|
|
return make_response('true', 200)
|
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():
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata('500.html')
|
2014-03-10 21:01:36 +00:00
|
|
|
|
2015-11-04 19:56:18 +00:00
|
|
|
@web.errorhandler(404)
|
|
|
|
@web.route('/404', methods=['GET'])
|
|
|
|
def not_found_error_display(e = None):
|
2016-12-07 22:13:17 +00:00
|
|
|
resp = index('', error_code=404, error_info=dict(reason='notfound'))
|
2015-11-04 19:56:18 +00:00
|
|
|
resp.status_code = 404
|
|
|
|
return resp
|
2014-03-10 21:01:36 +00:00
|
|
|
|
2015-04-10 19:35:23 +00:00
|
|
|
@web.route('/organization/<path:path>', methods=['GET'])
|
2017-09-28 19:14:53 +00:00
|
|
|
@web.route('/organization/<path:path>/', methods=['GET'])
|
2015-04-10 19:35:23 +00:00
|
|
|
@no_cache
|
|
|
|
def org_view(path):
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
|
|
|
@web.route('/user/<path:path>', methods=['GET'])
|
2017-09-28 19:14:53 +00:00
|
|
|
@web.route('/user/<path:path>/', methods=['GET'])
|
2015-04-10 19:35:23 +00:00
|
|
|
@no_cache
|
|
|
|
def user_view(path):
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2016-02-16 20:31:23 +00:00
|
|
|
@route_show_if(features.ACI_CONVERSION)
|
2015-02-04 20:29:24 +00:00
|
|
|
@web.route('/aci-signing-key')
|
|
|
|
@no_cache
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2015-02-04 20:29:24 +00:00
|
|
|
def aci_signing_key():
|
|
|
|
if not signer.name:
|
|
|
|
abort(404)
|
|
|
|
|
2017-04-05 03:28:49 +00:00
|
|
|
return send_file(signer.open_public_key_file(), mimetype=PGP_KEY_MIMETYPE)
|
2015-02-04 20:29:24 +00:00
|
|
|
|
2017-04-07 21:25:44 +00:00
|
|
|
|
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('')
|
|
|
|
|
|
|
|
|
2017-04-07 21:25:44 +00:00
|
|
|
@web.route('/search')
|
|
|
|
@no_cache
|
|
|
|
def search():
|
|
|
|
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-04-29 04:45:42 +00:00
|
|
|
@web.route('/tour/')
|
|
|
|
@web.route('/tour/<path:path>')
|
|
|
|
@no_cache
|
|
|
|
def tour(path = ''):
|
|
|
|
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
|
|
|
|
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
|
|
|
|
2015-01-23 22:19:15 +00:00
|
|
|
@web.route('/setup/')
|
|
|
|
@no_cache
|
|
|
|
@route_show_if(features.SUPER_USERS)
|
|
|
|
def setup():
|
|
|
|
return index('')
|
2014-04-10 04:26:55 +00:00
|
|
|
|
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
|
2014-08-18 21:24:00 +00:00
|
|
|
def signin(redirect=None):
|
2013-10-14 21:50:07 +00:00
|
|
|
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('')
|
|
|
|
|
|
|
|
|
2016-09-08 22:43:50 +00:00
|
|
|
@web.route('/updateuser')
|
|
|
|
@no_cache
|
|
|
|
def updateuser():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2014-08-18 21:24:00 +00:00
|
|
|
@web.route('/confirminvite')
|
|
|
|
@no_cache
|
|
|
|
def confirm_invite():
|
|
|
|
code = request.values['code']
|
|
|
|
return index('', code=code)
|
|
|
|
|
|
|
|
|
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('')
|
|
|
|
|
2016-09-27 14:52:34 +00:00
|
|
|
|
|
|
|
@web.route('/repository/<path:path>/trigger/<trigger>', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def buildtrigger(path, trigger):
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2017-03-23 21:16:19 +00:00
|
|
|
@route_show_if(features.APP_REGISTRY)
|
|
|
|
@web.route('/application/', defaults={'path': ''})
|
|
|
|
@web.route('/application/<path:path>', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def application(path):
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
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('')
|
|
|
|
|
|
|
|
|
2016-04-25 18:16:35 +00:00
|
|
|
@web.route('/enterprise/')
|
|
|
|
@no_cache
|
2016-05-04 19:20:27 +00:00
|
|
|
@route_show_if(features.BILLING)
|
2016-04-25 18:16:35 +00:00
|
|
|
def enterprise():
|
2017-03-30 19:25:56 +00:00
|
|
|
return redirect('/plans?tab=enterprise')
|
2016-04-25 18:16:35 +00:00
|
|
|
|
|
|
|
|
2015-02-23 19:48:33 +00:00
|
|
|
@web.route('/__exp/<expname>')
|
|
|
|
@no_cache
|
|
|
|
def exp(expname):
|
|
|
|
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
|
|
|
|
2016-06-09 17:45:37 +00:00
|
|
|
@web.route('/tos', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def tos():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
|
|
|
@web.route('/privacy', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def privacy():
|
|
|
|
return index('')
|
|
|
|
|
|
|
|
|
2015-01-20 21:58:29 +00:00
|
|
|
# TODO(jschorr): Remove this mirrored endpoint once we migrate ELB.
|
2014-11-02 20:06:17 +00:00
|
|
|
@web.route('/health', methods=['GET'])
|
2015-01-20 21:53:05 +00:00
|
|
|
@web.route('/health/instance', methods=['GET'])
|
2017-05-24 22:26:22 +00:00
|
|
|
@process_auth_or_cookie
|
2014-11-02 20:06:17 +00:00
|
|
|
@no_cache
|
2015-01-20 21:53:05 +00:00
|
|
|
def instance_health():
|
2016-07-05 18:14:22 +00:00
|
|
|
checker = get_healthchecker(app, config_provider, instance_keys)
|
2015-01-20 21:53:05 +00:00
|
|
|
(data, status_code) = checker.check_instance()
|
|
|
|
response = jsonify(dict(data=data, status_code=status_code))
|
|
|
|
response.status_code = status_code
|
2014-11-02 20:06:17 +00:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
2015-01-20 21:58:29 +00:00
|
|
|
# TODO(jschorr): Remove this mirrored endpoint once we migrate pingdom.
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/status', methods=['GET'])
|
2015-01-20 21:53:05 +00:00
|
|
|
@web.route('/health/endtoend', methods=['GET'])
|
2017-05-24 22:26:22 +00:00
|
|
|
@process_auth_or_cookie
|
2014-01-02 23:01:34 +00:00
|
|
|
@no_cache
|
2015-01-20 21:53:05 +00:00
|
|
|
def endtoend_health():
|
2016-07-05 18:14:22 +00:00
|
|
|
checker = get_healthchecker(app, config_provider, instance_keys)
|
2015-01-20 21:53:05 +00:00
|
|
|
(data, status_code) = checker.check_endtoend()
|
|
|
|
response = jsonify(dict(data=data, status_code=status_code))
|
|
|
|
response.status_code = status_code
|
2015-06-15 19:52:08 +00:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
@web.route('/health/dbrevision', methods=['GET'])
|
2015-06-16 21:43:02 +00:00
|
|
|
@route_show_if(features.BILLING) # Since this is only used in production.
|
2017-05-24 22:26:22 +00:00
|
|
|
@process_auth_or_cookie
|
2015-06-15 19:52:08 +00:00
|
|
|
@no_cache
|
|
|
|
def dbrevision_health():
|
|
|
|
# Find the revision from the database.
|
|
|
|
result = db.execute_sql('select * from alembic_version limit 1').fetchone()
|
|
|
|
db_revision = result[0]
|
|
|
|
|
|
|
|
# Find the local revision from the file system.
|
2017-02-01 23:17:25 +00:00
|
|
|
with open(os.path.join(ROOT_DIR, 'ALEMBIC_HEAD'), 'r') as f:
|
2015-06-15 19:52:08 +00:00
|
|
|
local_revision = f.readline().split(' ')[0]
|
|
|
|
|
|
|
|
data = {
|
|
|
|
'db_revision': db_revision,
|
|
|
|
'local_revision': local_revision,
|
|
|
|
}
|
|
|
|
|
|
|
|
status_code = 200 if db_revision == local_revision else 400
|
|
|
|
|
|
|
|
response = jsonify(dict(data=data, status_code=status_code))
|
|
|
|
response.status_code = status_code
|
2014-05-13 22:53:42 +00:00
|
|
|
return response
|
2013-10-02 18:35:21 +00:00
|
|
|
|
|
|
|
|
2017-05-24 22:26:22 +00:00
|
|
|
@web.route('/health/enabledebug/<secret>', methods=['GET'])
|
|
|
|
@no_cache
|
|
|
|
def enable_health_debug(secret):
|
|
|
|
if not secret:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
if not app.config.get('ENABLE_HEALTH_DEBUG_SECRET'):
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
if app.config.get('ENABLE_HEALTH_DEBUG_SECRET') != secret:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
session['health_debug'] = True
|
|
|
|
return make_response('Health check debug information enabled')
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-04-21 23:46:00 +00:00
|
|
|
@web.route('/robots.txt', methods=['GET'])
|
|
|
|
def robots():
|
2016-06-17 17:52:27 +00:00
|
|
|
robots_txt = make_response(render_template('robots.txt', baseurl=get_app_url()))
|
|
|
|
robots_txt.headers['Content-Type'] = 'text/plain'
|
|
|
|
return robots_txt
|
|
|
|
|
|
|
|
|
|
|
|
@web.route('/sitemap.xml', methods=['GET'])
|
|
|
|
def sitemap():
|
|
|
|
popular_repo_tuples = model.repository.list_popular_public_repos(50, timedelta(weeks=1))
|
|
|
|
xml = make_response(render_template('sitemap.xml', public_repos=popular_repo_tuples,
|
|
|
|
baseurl=get_app_url()))
|
|
|
|
xml.headers['Content-Type'] = 'application/xml'
|
|
|
|
return xml
|
2014-04-21 23:46:00 +00:00
|
|
|
|
|
|
|
|
2015-05-26 21:22:30 +00:00
|
|
|
@web.route('/buildlogs/<build_uuid>', methods=['GET'])
|
|
|
|
@route_show_if(features.BUILD_SUPPORT)
|
2017-08-18 17:45:36 +00:00
|
|
|
@process_auth_or_cookie
|
2015-05-26 21:22:30 +00:00
|
|
|
def buildlogs(build_uuid):
|
2015-07-15 21:25:41 +00:00
|
|
|
found_build = model.build.get_repository_build(build_uuid)
|
|
|
|
if not found_build:
|
2015-05-26 21:22:30 +00:00
|
|
|
abort(403)
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
repo = found_build.repository
|
2017-08-18 17:45:36 +00:00
|
|
|
has_permission = ModifyRepositoryPermission(repo.namespace_user.username, repo.name).can()
|
|
|
|
if features.READER_BUILD_LOGS and not has_permission:
|
|
|
|
if (ReadRepositoryPermission(repo.namespace_user.username, repo.name).can() or
|
|
|
|
model.repository.repository_is_public(repo.namespace_user.username, repo.name)):
|
|
|
|
has_permission = True
|
|
|
|
|
|
|
|
if not has_permission:
|
2015-05-26 21:22:30 +00:00
|
|
|
abort(403)
|
|
|
|
|
|
|
|
# If the logs have been archived, just return a URL of the completed archive
|
2015-07-15 21:25:41 +00:00
|
|
|
if found_build.logs_archived:
|
2017-10-05 19:20:16 +00:00
|
|
|
return redirect(log_archive.get_file_url(found_build.uuid, request.remote_addr))
|
2015-05-26 21:22:30 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
_, logs = build_logs.get_log_entries(found_build.uuid, 0)
|
2015-05-26 21:22:30 +00:00
|
|
|
response = jsonify({
|
|
|
|
'logs': [log for log in logs]
|
|
|
|
})
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
response.headers["Content-Disposition"] = "attachment;filename=" + found_build.uuid + ".json"
|
2015-05-26 21:22:30 +00:00
|
|
|
return response
|
|
|
|
|
|
|
|
|
2018-11-27 16:28:32 +00:00
|
|
|
@web.route('/exportedlogs/<file_id>', methods=['GET'])
|
|
|
|
def exportedlogs(file_id):
|
|
|
|
# Only enable this endpoint if local storage is available.
|
|
|
|
has_local_storage = False
|
|
|
|
for storage_type, _ in app.config.get('DISTRIBUTED_STORAGE_CONFIG', {}).values():
|
|
|
|
if storage_type == 'LocalStorage':
|
|
|
|
has_local_storage = True
|
|
|
|
break
|
|
|
|
|
|
|
|
if not has_local_storage:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
JSON_MIMETYPE = 'application/json'
|
|
|
|
exported_logs_storage_path = app.config.get('EXPORT_ACTION_LOGS_STORAGE_PATH',
|
|
|
|
'exportedactionlogs')
|
|
|
|
export_storage_path = os.path.join(exported_logs_storage_path, file_id)
|
|
|
|
if not storage.exists(storage.preferred_locations, export_storage_path):
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
try:
|
|
|
|
return send_file(storage.stream_read_file(storage.preferred_locations, export_storage_path),
|
|
|
|
mimetype=JSON_MIMETYPE)
|
|
|
|
except IOError:
|
|
|
|
logger.exception('Could not read exported logs')
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
|
2017-04-24 19:59:10 +00:00
|
|
|
@web.route('/logarchive/<file_id>', methods=['GET'])
|
|
|
|
@route_show_if(features.BUILD_SUPPORT)
|
2017-08-18 17:45:36 +00:00
|
|
|
@process_auth_or_cookie
|
2017-04-24 19:59:10 +00:00
|
|
|
def logarchive(file_id):
|
|
|
|
JSON_MIMETYPE = 'application/json'
|
|
|
|
try:
|
|
|
|
found_build = model.build.get_repository_build(file_id)
|
|
|
|
except model.InvalidRepositoryBuildException as ex:
|
|
|
|
logger.exception(ex, extra={'build_uuid': file_id})
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
repo = found_build.repository
|
2017-08-18 17:45:36 +00:00
|
|
|
has_permission = ModifyRepositoryPermission(repo.namespace_user.username, repo.name).can()
|
|
|
|
if features.READER_BUILD_LOGS and not has_permission:
|
|
|
|
if (ReadRepositoryPermission(repo.namespace_user.username, repo.name).can() or
|
|
|
|
model.repository.repository_is_public(repo.namespace_user.username, repo.name)):
|
|
|
|
has_permission = True
|
|
|
|
|
|
|
|
if not has_permission:
|
2017-04-24 19:59:10 +00:00
|
|
|
abort(403)
|
|
|
|
|
|
|
|
try:
|
|
|
|
path = log_archive.get_file_id_path(file_id)
|
|
|
|
data_stream = log_archive._storage.stream_read_file(log_archive._locations, path)
|
|
|
|
return send_file(GzipInputStream(data_stream), mimetype=JSON_MIMETYPE)
|
|
|
|
except IOError:
|
|
|
|
logger.exception('Could not read archived logs')
|
|
|
|
abort(403)
|
|
|
|
|
2018-11-27 16:28:32 +00:00
|
|
|
|
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():
|
2016-01-29 15:36:28 +00:00
|
|
|
if not current_user.is_authenticated:
|
2013-12-21 03:38:53 +00:00
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
2014-07-21 19:09:31 +00:00
|
|
|
invoice_id = request.args.get('id')
|
|
|
|
if invoice_id:
|
|
|
|
invoice = stripe.Invoice.retrieve(invoice_id)
|
2013-11-18 19:49:54 +00:00
|
|
|
if invoice:
|
2015-07-15 21:25:41 +00:00
|
|
|
user_or_org = model.user.get_user_or_org_by_customer_id(invoice.customer)
|
2014-11-26 15:54:16 +00:00
|
|
|
|
2013-12-21 03:38:53 +00:00
|
|
|
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)
|
2014-11-26 15:54:16 +00:00
|
|
|
return
|
2013-12-21 03:38:53 +00:00
|
|
|
|
2016-11-21 20:35:56 +00:00
|
|
|
def format_date(timestamp):
|
|
|
|
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d')
|
|
|
|
|
2014-11-26 15:54:16 +00:00
|
|
|
file_data = renderInvoiceToPdf(invoice, user_or_org)
|
2016-11-21 20:35:56 +00:00
|
|
|
receipt_filename = 'quay-receipt-%s.pdf' % (format_date(invoice.date))
|
2013-12-21 03:38:53 +00:00
|
|
|
return Response(file_data,
|
|
|
|
mimetype="application/pdf",
|
2016-11-21 20:35:56 +00:00
|
|
|
headers={"Content-Disposition": "attachment;filename=" + receipt_filename})
|
2013-11-18 19:49:54 +00:00
|
|
|
abort(404)
|
|
|
|
|
2013-09-27 23:55:04 +00:00
|
|
|
|
2014-07-28 18:58:12 +00:00
|
|
|
@web.route('/authrepoemail', methods=['GET'])
|
2014-09-22 23:11:48 +00:00
|
|
|
@route_show_if(features.MAILING)
|
2014-07-28 18:58:12 +00:00
|
|
|
def confirm_repo_email():
|
|
|
|
code = request.values['code']
|
|
|
|
record = None
|
|
|
|
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
record = model.repository.confirm_email_authorization_for_repo(code)
|
2015-07-29 22:28:58 +00:00
|
|
|
except model.DataModelException as ex:
|
2016-12-07 22:13:17 +00:00
|
|
|
return index('', error_info=dict(reason='confirmerror', error_message=ex.message))
|
2014-11-26 15:54:16 +00:00
|
|
|
|
2014-07-28 18:58:12 +00:00
|
|
|
message = """
|
|
|
|
Your E-mail address has been authorized to receive notifications for repository
|
|
|
|
<a href="%s://%s/repository/%s/%s">%s/%s</a>.
|
|
|
|
""" % (app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'],
|
2014-09-24 22:01:35 +00:00
|
|
|
record.repository.namespace_user.username, record.repository.name,
|
|
|
|
record.repository.namespace_user.username, record.repository.name)
|
2014-07-28 18:58:12 +00:00
|
|
|
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata('message.html', message=message)
|
2014-07-28 18:58:12 +00:00
|
|
|
|
|
|
|
|
2013-12-30 22:05:27 +00:00
|
|
|
@web.route('/confirm', methods=['GET'])
|
2014-09-22 23:11:48 +00:00
|
|
|
@route_show_if(features.MAILING)
|
2015-11-10 20:41:19 +00:00
|
|
|
@anon_allowed
|
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:
|
2015-07-15 21:25:41 +00:00
|
|
|
user, new_email, old_email = model.user.confirm_user_email(code)
|
2015-07-29 22:28:58 +00:00
|
|
|
except model.DataModelException as ex:
|
2016-12-07 22:13:17 +00:00
|
|
|
return index('', error_info=dict(reason='confirmerror', error_message=ex.message))
|
2014-11-26 15:54:16 +00:00
|
|
|
|
2014-09-05 23:57:33 +00:00
|
|
|
if new_email:
|
|
|
|
send_email_changed(user.username, old_email, new_email)
|
2017-04-05 15:25:29 +00:00
|
|
|
change_email_future = user_analytics.change_email(old_email, new_email)
|
|
|
|
change_email_future.add_done_callback(build_error_callback('Change email failed'))
|
2014-09-05 23:57:33 +00:00
|
|
|
|
2017-07-20 19:40:14 +00:00
|
|
|
common_login(user.uuid)
|
2016-11-04 21:57:55 +00:00
|
|
|
if model.user.has_user_prompts(user):
|
|
|
|
return redirect(url_for('web.updateuser'))
|
2016-11-28 23:55:41 +00:00
|
|
|
elif new_email:
|
|
|
|
return redirect(url_for('web.user_view', path=user.username, tab='settings'))
|
2016-11-04 21:57:55 +00:00
|
|
|
else:
|
2016-11-28 23:55:41 +00:00
|
|
|
return redirect(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'])
|
2015-11-10 20:41:19 +00:00
|
|
|
@route_show_if(features.MAILING)
|
|
|
|
@anon_allowed
|
2013-10-14 21:50:07 +00:00
|
|
|
def confirm_recovery():
|
|
|
|
code = request.values['code']
|
2015-07-15 21:25:41 +00:00
|
|
|
user = model.user.validate_reset_code(code)
|
2013-09-27 23:29:01 +00:00
|
|
|
|
2016-03-30 20:02:47 +00:00
|
|
|
if user is not None:
|
2017-07-20 19:40:14 +00:00
|
|
|
common_login(user.uuid)
|
2016-11-28 23:55:41 +00:00
|
|
|
return redirect(url_for('web.user_view', path=user.username, tab='settings', action='password'))
|
2013-10-14 21:50:07 +00:00
|
|
|
else:
|
2016-03-30 20:02:47 +00:00
|
|
|
message = 'Invalid recovery code: This code is invalid or may have already been used.'
|
|
|
|
return render_page_template_with_routedata('message.html', message=message)
|
2014-02-28 21:23:36 +00:00
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@web.route('/repository/<repopath:repository>/status', methods=['GET'])
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2016-03-09 21:20:28 +00:00
|
|
|
def build_status_badge(namespace_name, repo_name):
|
2014-02-28 21:23:36 +00:00
|
|
|
token = request.args.get('token', None)
|
2017-03-22 19:29:44 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
|
|
|
if repo and repo.kind.name != 'image':
|
|
|
|
abort(404)
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
is_public = model.repository.repository_is_public(namespace_name, repo_name)
|
2014-02-28 21:23:36 +00:00
|
|
|
if not is_public:
|
|
|
|
if not repo or token != repo.badge_token:
|
|
|
|
abort(404)
|
|
|
|
|
2017-06-07 19:05:29 +00:00
|
|
|
is_empty = model.repository.is_empty(namespace_name, repo_name)
|
2016-03-09 21:20:28 +00:00
|
|
|
recent_build = model.build.get_recent_repository_build(namespace_name, repo_name)
|
2015-07-15 21:25:41 +00:00
|
|
|
if not is_empty and (not recent_build or recent_build.phase == 'complete'):
|
2014-03-05 19:57:14 +00:00
|
|
|
status_name = 'ready'
|
2015-07-15 21:25:41 +00:00
|
|
|
elif recent_build and recent_build.phase == 'error':
|
2014-03-05 19:57:14 +00:00
|
|
|
status_name = 'failed'
|
2016-11-16 18:51:07 +00:00
|
|
|
elif recent_build and recent_build.phase == 'cancelled':
|
|
|
|
status_name = 'cancelled'
|
2015-07-15 21:25:41 +00:00
|
|
|
elif recent_build and recent_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
|
|
|
|
2015-11-06 17:15:15 +00:00
|
|
|
if request.headers.get('If-None-Match') == status_name:
|
|
|
|
return Response(status=304)
|
|
|
|
|
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'
|
2015-11-06 17:15:15 +00:00
|
|
|
response.headers['Cache-Control'] = 'no-cache'
|
|
|
|
response.headers['ETag'] = status_name
|
2014-02-28 21:23:36 +00:00
|
|
|
return response
|
2014-03-12 16:37:06 +00:00
|
|
|
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
class FlaskAuthorizationProvider(model.oauth.DatabaseAuthorizationProvider):
|
2014-03-12 16:37:06 +00:00
|
|
|
def get_authorized_user(self):
|
2016-05-13 18:52:22 +00:00
|
|
|
return get_authenticated_user()
|
2014-03-12 16:37:06 +00:00
|
|
|
|
|
|
|
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'])
|
2016-05-13 18:52:22 +00:00
|
|
|
@process_auth_or_cookie
|
2014-03-14 22:57:28 +00:00
|
|
|
def authorize_application():
|
2016-05-13 18:52:22 +00:00
|
|
|
# Check for an authenticated user.
|
|
|
|
if not get_authenticated_user():
|
2014-03-14 22:57:28 +00:00
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
2016-05-13 18:52:22 +00:00
|
|
|
# If direct OAuth is not enabled or the user is not directly authed, verify CSRF.
|
2014-03-14 22:57:28 +00:00
|
|
|
client_id = request.form.get('client_id', None)
|
2016-05-13 18:52:22 +00:00
|
|
|
whitelist = app.config.get('DIRECT_OAUTH_CLIENTID_WHITELIST', [])
|
|
|
|
if client_id not in whitelist or not has_basic_auth(get_authenticated_user().username):
|
|
|
|
verify_csrf()
|
|
|
|
|
|
|
|
provider = FlaskAuthorizationProvider()
|
2014-03-14 22:57:28 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2015-06-01 17:43:38 +00:00
|
|
|
@web.route(app.config['LOCAL_OAUTH_HANDLER'], methods=['GET'])
|
|
|
|
def oauth_local_handler():
|
2016-01-29 15:36:28 +00:00
|
|
|
if not current_user.is_authenticated:
|
2015-06-01 17:43:38 +00:00
|
|
|
abort(401)
|
|
|
|
return
|
|
|
|
|
|
|
|
if not request.args.get('scope'):
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata("message.html", message="Authorization canceled")
|
2015-06-01 17:43:38 +00:00
|
|
|
else:
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata("generatedtoken.html")
|
2015-06-01 17:43:38 +00:00
|
|
|
|
|
|
|
|
2014-03-24 22:30:22 +00:00
|
|
|
@web.route('/oauth/denyapp', methods=['POST'])
|
2016-12-08 21:11:57 +00:00
|
|
|
@csrf_protect()
|
2014-03-24 22:30:22 +00:00
|
|
|
def deny_application():
|
2016-01-29 15:36:28 +00:00
|
|
|
if not current_user.is_authenticated:
|
2014-03-24 22:30:22 +00:00
|
|
|
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
|
2014-07-21 19:09:31 +00:00
|
|
|
@param_required('client_id')
|
|
|
|
@param_required('redirect_uri')
|
|
|
|
@param_required('scope')
|
2016-11-17 21:21:26 +00:00
|
|
|
@process_auth_or_cookie
|
2014-03-12 16:37:06 +00:00
|
|
|
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)
|
|
|
|
|
2016-01-29 15:36:28 +00:00
|
|
|
if (not current_user.is_authenticated or
|
2014-03-19 22:09:09 +00:00
|
|
|
not provider.validate_has_scopes(client_id, current_user.db_user().username, scope)):
|
2015-06-01 17:43:38 +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)
|
2015-04-24 20:42:31 +00:00
|
|
|
app_email = oauth_app.avatar_email or oauth_app.organization.email
|
2015-03-30 21:55:04 +00:00
|
|
|
|
2014-03-14 22:57:28 +00:00
|
|
|
oauth_app_view = {
|
|
|
|
'name': oauth_app.name,
|
|
|
|
'description': oauth_app.description,
|
|
|
|
'url': oauth_app.application_uri,
|
2015-04-24 20:42:31 +00:00
|
|
|
'avatar': json.dumps(avatar.get_data(oauth_app.name, app_email, 'app')),
|
2014-03-14 22:57:28 +00:00
|
|
|
'organization': {
|
|
|
|
'name': oauth_app.organization.username,
|
2015-04-24 20:42:31 +00:00
|
|
|
'avatar': json.dumps(avatar.get_data_for_org(oauth_app.organization))
|
2014-03-14 22:57:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Show the authorization page.
|
2014-08-06 22:51:04 +00:00
|
|
|
has_dangerous_scopes = any([check_scope['dangerous'] for check_scope in scope_info])
|
2016-01-08 18:53:27 +00:00
|
|
|
return render_page_template_with_routedata('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())
|
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
|
2015-05-18 16:38:39 +00:00
|
|
|
@param_required('grant_type', allow_body=True)
|
|
|
|
@param_required('client_id', allow_body=True)
|
|
|
|
@param_required('redirect_uri', allow_body=True)
|
|
|
|
@param_required('code', allow_body=True)
|
|
|
|
@param_required('scope', allow_body=True)
|
2014-03-25 16:42:40 +00:00
|
|
|
def exchange_code_for_token():
|
2015-05-18 16:38:39 +00:00
|
|
|
grant_type = request.values.get('grant_type', None)
|
|
|
|
client_id = request.values.get('client_id', None)
|
2015-05-18 19:01:37 +00:00
|
|
|
client_secret = request.values.get('client_id', None)
|
2015-05-18 16:38:39 +00:00
|
|
|
redirect_uri = request.values.get('redirect_uri', None)
|
|
|
|
code = request.values.get('code', None)
|
|
|
|
scope = request.values.get('scope', None)
|
|
|
|
|
2015-05-18 19:01:37 +00:00
|
|
|
# Sometimes OAuth2 clients place the client id/secret in the Auth header.
|
|
|
|
basic_header = parse_basic_auth(request.headers.get('Authorization'))
|
|
|
|
if basic_header is not None:
|
|
|
|
client_id = basic_header[0] or client_id
|
|
|
|
client_secret = basic_header[1] or client_secret
|
2014-03-25 16:42:40 +00:00
|
|
|
|
|
|
|
provider = FlaskAuthorizationProvider()
|
|
|
|
return provider.get_token(grant_type, client_id, client_secret, redirect_uri, code, scope=scope)
|
2014-12-23 19:01:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
@web.route('/systemlogsarchive', methods=['GET'])
|
|
|
|
@process_oauth
|
|
|
|
@route_show_if(features.SUPER_USERS)
|
|
|
|
@no_cache
|
|
|
|
def download_logs_archive():
|
|
|
|
# Note: We cannot use the decorator here because this is a GET method. That being said, this
|
|
|
|
# information is sensitive enough that we want the extra protection.
|
|
|
|
verify_csrf()
|
|
|
|
|
|
|
|
if SuperUserPermission().can():
|
|
|
|
archive_data = build_logs_archive(app)
|
|
|
|
return Response(archive_data,
|
|
|
|
mimetype="application/octet-stream",
|
|
|
|
headers={"Content-Disposition": "attachment;filename=erlogs.tar.gz"})
|
|
|
|
|
|
|
|
abort(403)
|
2015-03-27 21:07:06 +00:00
|
|
|
|
2015-04-24 19:13:08 +00:00
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@web.route('/bitbucket/setup/<repopath:repository>', methods=['GET'])
|
2015-04-24 19:13:08 +00:00
|
|
|
@require_session_login
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
2015-04-24 19:13:08 +00:00
|
|
|
@route_show_if(features.BITBUCKET_BUILD)
|
2016-03-09 21:20:28 +00:00
|
|
|
def attach_bitbucket_trigger(namespace_name, repo_name):
|
|
|
|
permission = AdministerRepositoryPermission(namespace_name, repo_name)
|
2015-04-24 19:13:08 +00:00
|
|
|
if permission.can():
|
2016-03-09 21:20:28 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
2015-04-24 19:13:08 +00:00
|
|
|
if not repo:
|
2016-03-09 21:20:28 +00:00
|
|
|
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
|
2015-04-24 19:13:08 +00:00
|
|
|
abort(404, message=msg)
|
2017-03-22 19:29:44 +00:00
|
|
|
elif repo.kind.name != 'image':
|
|
|
|
abort(501)
|
2015-04-24 19:13:08 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
|
|
|
|
current_user.db_user())
|
2015-04-24 19:13:08 +00:00
|
|
|
|
|
|
|
try:
|
2015-04-24 22:36:48 +00:00
|
|
|
oauth_info = BuildTriggerHandler.get_handler(trigger).get_oauth_url()
|
2016-02-01 12:44:22 +00:00
|
|
|
except TriggerProviderException:
|
|
|
|
trigger.delete_instance()
|
|
|
|
logger.debug('Could not retrieve Bitbucket OAuth URL')
|
|
|
|
abort(500)
|
2015-04-24 19:13:08 +00:00
|
|
|
|
2016-02-01 12:44:22 +00:00
|
|
|
config = {
|
|
|
|
'access_token': oauth_info['access_token']
|
|
|
|
}
|
2015-04-24 19:13:08 +00:00
|
|
|
|
2016-02-01 12:44:22 +00:00
|
|
|
access_token_secret = oauth_info['access_token_secret']
|
|
|
|
model.build.update_build_trigger(trigger, config, auth_token=access_token_secret)
|
2015-04-24 19:13:08 +00:00
|
|
|
|
2016-02-01 12:44:22 +00:00
|
|
|
return redirect(oauth_info['url'])
|
2015-04-24 19:13:08 +00:00
|
|
|
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@web.route('/customtrigger/setup/<repopath:repository>', methods=['GET'])
|
2015-03-27 21:07:06 +00:00
|
|
|
@require_session_login
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name()
|
|
|
|
def attach_custom_build_trigger(namespace_name, repo_name):
|
|
|
|
permission = AdministerRepositoryPermission(namespace_name, repo_name)
|
2015-03-27 21:07:06 +00:00
|
|
|
if permission.can():
|
2016-03-09 21:20:28 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
2015-03-27 21:07:06 +00:00
|
|
|
if not repo:
|
2016-03-09 21:20:28 +00:00
|
|
|
msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
|
2015-03-27 21:07:06 +00:00
|
|
|
abort(404, message=msg)
|
2017-03-22 19:29:44 +00:00
|
|
|
elif repo.kind.name != 'image':
|
|
|
|
abort(501)
|
2015-03-27 21:07:06 +00:00
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(),
|
|
|
|
None, current_user.db_user())
|
2015-03-27 21:07:06 +00:00
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
repo_path = '%s/%s' % (namespace_name, repo_name)
|
2016-09-27 14:52:34 +00:00
|
|
|
full_url = url_for('web.buildtrigger', path=repo_path, trigger=trigger.uuid)
|
2015-03-27 21:07:06 +00:00
|
|
|
logger.debug('Redirecting to full url: %s', full_url)
|
|
|
|
return redirect(full_url)
|
|
|
|
|
|
|
|
abort(403)
|
2015-04-10 19:35:23 +00:00
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@web.route('/<repopath:repository>')
|
2017-09-28 19:14:53 +00:00
|
|
|
@web.route('/<repopath:repository>/')
|
2015-04-10 19:35:23 +00:00
|
|
|
@no_cache
|
|
|
|
@process_oauth
|
2016-03-09 21:20:28 +00:00
|
|
|
@parse_repository_name(include_tag=True)
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2016-03-09 21:20:28 +00:00
|
|
|
def redirect_to_repository(namespace_name, repo_name, tag_name):
|
2016-09-21 17:53:09 +00:00
|
|
|
# Always return 200 for ac-discovery, to ensure that rkt and other ACI-compliant clients can
|
|
|
|
# find the metadata they need. Permissions will be checked in the registry API.
|
2015-04-30 17:03:50 +00:00
|
|
|
if request.args.get('ac-discovery', 0) == 1:
|
|
|
|
return index('')
|
|
|
|
|
2016-09-21 17:53:09 +00:00
|
|
|
# Redirect to the repository page if the user can see the repository.
|
|
|
|
is_public = model.repository.repository_is_public(namespace_name, repo_name)
|
|
|
|
permission = ReadRepositoryPermission(namespace_name, repo_name)
|
2017-04-05 18:29:46 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
2016-09-21 17:53:09 +00:00
|
|
|
|
2017-04-05 18:29:46 +00:00
|
|
|
if repo and (permission.can() or is_public):
|
2016-03-09 21:20:28 +00:00
|
|
|
repo_path = '/'.join([namespace_name, repo_name])
|
2017-04-05 18:29:46 +00:00
|
|
|
if repo.kind.name == 'application':
|
|
|
|
return redirect(url_for('web.application', path=repo_path))
|
|
|
|
else:
|
|
|
|
return redirect(url_for('web.repository', path=repo_path, tab="tags", tag=tag_name))
|
2015-04-10 19:35:23 +00:00
|
|
|
|
2016-09-21 17:53:09 +00:00
|
|
|
namespace_exists = bool(model.user.get_user_or_org(namespace_name))
|
|
|
|
namespace_permission = OrganizationMemberPermission(namespace_name).can()
|
|
|
|
if get_authenticated_user() and get_authenticated_user().username == namespace_name:
|
|
|
|
namespace_permission = True
|
|
|
|
|
|
|
|
# Otherwise, we display an error for the user. Which error we display depends on permissions:
|
|
|
|
# > If the namespace doesn't exist, 404.
|
|
|
|
# > If the user is a member of the namespace:
|
|
|
|
# - If the repository doesn't exist, 404
|
|
|
|
# - If the repository does exist (no access), 403
|
|
|
|
# > If the user is not a member of the namespace: 403
|
|
|
|
error_info = {
|
2016-12-07 22:13:17 +00:00
|
|
|
'reason': 'notfound',
|
2016-09-21 17:53:09 +00:00
|
|
|
'for_repo': True,
|
|
|
|
'namespace_exists': namespace_exists,
|
|
|
|
'namespace': namespace_name,
|
|
|
|
'repo_name': repo_name,
|
|
|
|
}
|
|
|
|
|
2017-04-13 17:54:20 +00:00
|
|
|
if not namespace_exists or (namespace_permission and repo is None):
|
2016-09-21 17:53:09 +00:00
|
|
|
resp = index('', error_code=404, error_info=json.dumps(error_info))
|
|
|
|
resp.status_code = 404
|
|
|
|
return resp
|
|
|
|
else:
|
|
|
|
resp = index('', error_code=403, error_info=json.dumps(error_info))
|
|
|
|
resp.status_code = 403
|
|
|
|
return resp
|
2015-04-10 19:35:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@web.route('/<namespace>')
|
2017-09-28 19:14:53 +00:00
|
|
|
@web.route('/<namespace>/')
|
2015-04-10 19:35:23 +00:00
|
|
|
@no_cache
|
|
|
|
@process_oauth
|
2015-05-19 21:52:44 +00:00
|
|
|
@anon_protect
|
2015-04-10 19:35:23 +00:00
|
|
|
def redirect_to_namespace(namespace):
|
2015-07-15 21:25:41 +00:00
|
|
|
user_or_org = model.user.get_user_or_org(namespace)
|
2015-04-10 19:35:23 +00:00
|
|
|
if not user_or_org:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
if user_or_org.organization:
|
|
|
|
return redirect(url_for('web.org_view', path=namespace))
|
|
|
|
else:
|
|
|
|
return redirect(url_for('web.user_view', path=namespace))
|