import logging import json from flask import request, make_response, Blueprint from app import billing as stripe from data import model from auth.auth import process_auth from auth.permissions import ModifyRepositoryPermission from util.invoice import renderInvoiceToHtml from util.useremails import send_invoice_email, send_subscription_change, send_payment_failed from util.names import parse_repository_name from util.http import abort from endpoints.trigger import BuildTrigger, ValidationRequestException, SkipRequestException from endpoints.common import start_build logger = logging.getLogger(__name__) webhooks = Blueprint('webhooks', __name__) @webhooks.route('/stripe', methods=['POST']) def stripe_webhook(): request_data = request.get_json() logger.debug('Stripe webhook call: %s' % request_data) customer_id = request_data.get('data', {}).get('object', {}).get('customer', None) user = model.get_user_or_org_by_customer_id(customer_id) if customer_id else None event_type = request_data['type'] if 'type' in request_data else None if event_type == 'charge.succeeded': invoice_id = request_data['data']['object']['invoice'] if user and user.invoice_email: # Lookup the invoice. invoice = stripe.Invoice.retrieve(invoice_id) if invoice: invoice_html = renderInvoiceToHtml(invoice, user) send_invoice_email(user.email, invoice_html) elif event_type.startswith('customer.subscription.'): cust_email = user.email if user is not None else 'unknown@domain.com' quay_username = user.username if user is not None else 'unknown' 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) elif event_type == 'invoice.payment_failed': if user: send_payment_failed(user.email, user.username) return make_response('Okay') @webhooks.route('/push//trigger/', methods=['POST']) @process_auth def build_trigger_webhook(_, trigger_uuid): logger.debug('Webhook received with uuid %s', trigger_uuid) try: trigger = model.get_build_trigger(trigger_uuid) 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 permission = ModifyRepositoryPermission(namespace, repository) if permission.can(): handler = BuildTrigger.get_trigger_for_service(trigger.service.name) logger.debug('Passing webhook request to handler %s', handler) config_dict = json.loads(trigger.config) try: specs = handler.handle_trigger_request(request, trigger.auth_token, config_dict) dockerfile_id, tags, name, subdir = specs except ValidationRequestException: # This was just a validation request, we don't need to build anything return make_response('Okay') except SkipRequestException: # The build was requested to be skipped return make_response('Okay') pull_robot_name = model.get_pull_robot_name(trigger) repo = model.get_repository(namespace, repository) start_build(repo, dockerfile_id, tags, name, subdir, False, trigger, pull_robot_name=pull_robot_name) return make_response('Okay') abort(403)