2013-11-15 14:42:31 -05:00
|
|
|
import logging
|
|
|
|
|
2013-12-30 17:05:27 -05:00
|
|
|
from flask import request, make_response, Blueprint
|
2013-11-15 14:42:31 -05:00
|
|
|
|
2014-04-10 15:20:16 -04:00
|
|
|
from app import billing as stripe
|
2013-11-15 14:42:31 -05:00
|
|
|
from data import model
|
2017-03-16 16:50:09 -04:00
|
|
|
from auth.decorators import process_auth
|
2014-02-11 13:53:44 -05:00
|
|
|
from auth.permissions import ModifyRepositoryPermission
|
2013-11-15 14:42:31 -05:00
|
|
|
from util.invoice import renderInvoiceToHtml
|
2014-05-28 18:22:48 -04:00
|
|
|
from util.useremails import send_invoice_email, send_subscription_change, send_payment_failed
|
2014-02-11 13:53:44 -05:00
|
|
|
from util.http import abort
|
2015-09-11 17:40:32 -04:00
|
|
|
from buildtrigger.basehandler import BuildTriggerHandler
|
2016-12-05 16:07:00 -05:00
|
|
|
from buildtrigger.triggerutil import (ValidationRequestException, SkipRequestException,
|
2015-09-11 17:40:32 -04:00
|
|
|
InvalidPayloadException)
|
2016-12-05 16:07:00 -05:00
|
|
|
from endpoints.building import start_build, MaximumBuildsQueuedException
|
2014-02-18 15:50:15 -05:00
|
|
|
|
2013-12-19 17:06:04 -05:00
|
|
|
|
2013-11-15 14:42:31 -05:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2013-12-30 17:05:27 -05:00
|
|
|
webhooks = Blueprint('webhooks', __name__)
|
2013-11-15 14:42:31 -05:00
|
|
|
|
2014-02-18 15:50:15 -05:00
|
|
|
|
2013-12-30 17:05:27 -05:00
|
|
|
@webhooks.route('/stripe', methods=['POST'])
|
2013-11-15 14:42:31 -05:00
|
|
|
def stripe_webhook():
|
|
|
|
request_data = request.get_json()
|
2015-03-23 12:14:47 -04:00
|
|
|
logger.debug('Stripe webhook call: %s', request_data)
|
2013-11-15 14:42:31 -05:00
|
|
|
|
2014-04-22 13:56:34 -04:00
|
|
|
customer_id = request_data.get('data', {}).get('object', {}).get('customer', None)
|
2015-07-15 17:25:41 -04:00
|
|
|
user = model.user.get_user_or_org_by_customer_id(customer_id) if customer_id else None
|
2014-04-22 13:56:34 -04:00
|
|
|
|
2013-11-15 14:42:31 -05:00
|
|
|
event_type = request_data['type'] if 'type' in request_data else None
|
|
|
|
if event_type == 'charge.succeeded':
|
2014-04-28 12:47:48 -04:00
|
|
|
invoice_id = request_data['data']['object']['invoice']
|
2014-04-22 13:56:34 -04:00
|
|
|
|
|
|
|
if user and user.invoice_email:
|
|
|
|
# Lookup the invoice.
|
|
|
|
invoice = stripe.Invoice.retrieve(invoice_id)
|
|
|
|
if invoice:
|
|
|
|
invoice_html = renderInvoiceToHtml(invoice, user)
|
2015-12-28 13:59:50 -05:00
|
|
|
send_invoice_email(user.invoice_email_address or user.email, invoice_html)
|
2014-04-22 13:56:34 -04:00
|
|
|
|
2014-04-15 17:00:32 -04:00
|
|
|
elif event_type.startswith('customer.subscription.'):
|
2014-04-22 13:56:34 -04:00
|
|
|
cust_email = user.email if user is not None else 'unknown@domain.com'
|
|
|
|
quay_username = user.username if user is not None else 'unknown'
|
2014-04-17 16:18:37 -04:00
|
|
|
|
|
|
|
change_type = ''
|
|
|
|
if event_type.endswith('.deleted'):
|
|
|
|
plan_id = request_data['data']['object']['plan']['id']
|
|
|
|
change_type = 'canceled %s' % plan_id
|
|
|
|
send_subscription_change(change_type, customer_id, cust_email, quay_username)
|
|
|
|
elif event_type.endswith('.created'):
|
|
|
|
plan_id = request_data['data']['object']['plan']['id']
|
|
|
|
change_type = 'subscribed %s' % plan_id
|
|
|
|
send_subscription_change(change_type, customer_id, cust_email, quay_username)
|
|
|
|
elif event_type.endswith('.updated'):
|
|
|
|
if 'previous_attributes' in request_data['data']:
|
|
|
|
if 'plan' in request_data['data']['previous_attributes']:
|
|
|
|
old_plan = request_data['data']['previous_attributes']['plan']['id']
|
|
|
|
new_plan = request_data['data']['object']['plan']['id']
|
|
|
|
change_type = 'switched %s -> %s' % (old_plan, new_plan)
|
|
|
|
send_subscription_change(change_type, customer_id, cust_email, quay_username)
|
2013-11-15 14:42:31 -05:00
|
|
|
|
2014-04-22 13:56:34 -04:00
|
|
|
elif event_type == 'invoice.payment_failed':
|
|
|
|
if user:
|
|
|
|
send_payment_failed(user.email, user.username)
|
|
|
|
|
2013-11-15 14:42:31 -05:00
|
|
|
return make_response('Okay')
|
2014-02-11 13:53:44 -05:00
|
|
|
|
2014-02-18 15:50:15 -05:00
|
|
|
|
2016-01-21 15:40:51 -05:00
|
|
|
@webhooks.route('/push/<repopath:repository>/trigger/<trigger_uuid>', methods=['POST'])
|
2014-11-18 10:24:48 -05:00
|
|
|
@webhooks.route('/push/trigger/<trigger_uuid>', methods=['POST'], defaults={'repository': ''})
|
2014-02-11 13:53:44 -05:00
|
|
|
@process_auth
|
2014-11-18 10:24:48 -05:00
|
|
|
def build_trigger_webhook(trigger_uuid, **kwargs):
|
2014-11-09 17:50:57 -05:00
|
|
|
logger.debug('Webhook received with uuid %s', trigger_uuid)
|
2014-11-24 16:07:38 -05:00
|
|
|
|
2014-11-09 17:50:57 -05:00
|
|
|
try:
|
2015-07-15 17:25:41 -04:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-11-09 17:50:57 -05:00
|
|
|
except model.InvalidBuildTriggerException:
|
|
|
|
# It is ok to return 404 here, since letting an attacker know that a trigger UUID is valid
|
|
|
|
# doesn't leak anything
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
namespace = trigger.repository.namespace_user.username
|
|
|
|
repository = trigger.repository.name
|
2014-02-11 13:53:44 -05:00
|
|
|
permission = ModifyRepositoryPermission(namespace, repository)
|
2017-03-20 13:22:59 -04:00
|
|
|
|
2014-05-01 21:35:07 -04:00
|
|
|
if permission.can():
|
2015-04-24 18:36:48 -04:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
2014-02-18 15:50:15 -05:00
|
|
|
|
2017-04-16 22:40:59 -04:00
|
|
|
if trigger.repository.kind.name != 'image':
|
|
|
|
abort(501, 'Build triggers cannot be invoked on application repositories')
|
|
|
|
elif trigger.repository.trust_enabled:
|
|
|
|
abort(400, 'Build triggers cannot be invoked on repositories with trust enabled')
|
|
|
|
|
2014-02-18 15:50:15 -05:00
|
|
|
logger.debug('Passing webhook request to handler %s', handler)
|
2014-02-21 17:09:56 -05:00
|
|
|
try:
|
2015-04-29 17:04:52 -04:00
|
|
|
prepared = handler.handle_trigger_request(request)
|
2014-02-21 17:09:56 -05:00
|
|
|
except ValidationRequestException:
|
2015-05-10 13:38:47 -04:00
|
|
|
logger.debug('Handler reported a validation exception: %s', handler)
|
2014-02-24 16:11:23 -05:00
|
|
|
# This was just a validation request, we don't need to build anything
|
2014-02-21 17:09:56 -05:00
|
|
|
return make_response('Okay')
|
2014-05-01 15:25:46 -04:00
|
|
|
except SkipRequestException:
|
2015-05-10 13:38:47 -04:00
|
|
|
logger.debug('Handler reported to skip the build: %s', handler)
|
2014-05-01 15:25:46 -04:00
|
|
|
# The build was requested to be skipped
|
|
|
|
return make_response('Okay')
|
2015-05-10 13:38:47 -04:00
|
|
|
except InvalidPayloadException as ipe:
|
|
|
|
logger.exception('Invalid payload')
|
2015-03-26 16:20:53 -04:00
|
|
|
# The payload was malformed
|
2015-05-10 13:38:47 -04:00
|
|
|
abort(400, message=ipe.message)
|
2014-05-01 15:25:46 -04:00
|
|
|
|
2015-07-15 17:25:41 -04:00
|
|
|
pull_robot_name = model.build.get_pull_robot_name(trigger)
|
|
|
|
repo = model.repository.get_repository(namespace, repository)
|
2016-12-05 16:07:00 -05:00
|
|
|
try:
|
|
|
|
start_build(repo, prepared, pull_robot_name=pull_robot_name)
|
|
|
|
except MaximumBuildsQueuedException:
|
2016-12-06 20:40:54 -05:00
|
|
|
abort(429, message='Maximum queued build rate exceeded.')
|
2014-02-25 18:22:55 -05:00
|
|
|
|
2014-02-18 15:50:15 -05:00
|
|
|
return make_response('Okay')
|
2014-02-11 13:53:44 -05:00
|
|
|
|
2014-02-25 18:22:55 -05:00
|
|
|
abort(403)
|