def obj_create(self, bundle, request, **kwargs): form = CreateBillingConfigurationForm(bundle.data) if not form.is_valid(): raise self.form_errors(form) client = get_client() billing = client.client('billing') data = form.bango_data types = billing.factory.create('ArrayOfString') for f in PAYMENT_TYPES: types.string.append(f) data['typeFilter'] = types price_list = billing.factory.create('ArrayOfPrice') for item in form.cleaned_data['prices']: price = billing.factory.create('Price') price.amount = item.cleaned_data['amount'] price.currency = item.cleaned_data['currency'] price_list.Price.append(price) data['priceList'] = price_list config = billing.factory.create('ArrayOfBillingConfigurationOption') configs = { 'APPLICATION_CATEGORY_ID': '18', 'APPLICATION_SIZE_KB': 2, 'BILLING_CONFIGURATION_TIME_OUT': 120, 'REDIRECT_URL_ONSUCCESS': data.pop('redirect_url_onsuccess'), 'REDIRECT_URL_ONERROR': data.pop('redirect_url_onerror'), 'REQUEST_SIGNATURE': sign(data['externalTransactionId']), } for k, v in configs.items(): opt = billing.factory.create('BillingConfigurationOption') opt.configurationOptionName = k opt.configurationOptionValue = v config.BillingConfigurationOption.append(opt) data['configurationOptions'] = config resp = get_client().CreateBillingConfiguration(data) bundle.data = {'responseCode': resp.responseCode, 'responseMessage': resp.responseMessage, 'billingConfigurationId': resp.billingConfigurationId} create_data = data.copy() create_data['transaction_uuid'] = data.pop('externalTransactionId') create.send(sender=self, bundle=bundle, data=create_data, form=form) return bundle
def _check_for_tampering(self, tok, cleaned_data): """ Use the token service to see if any data has been tampered with. """ cli = get_client().client('token_checker') with statsd.timer('solitude.bango.request.checktoken'): true_data = cli.service.CheckToken(token=tok) if true_data.ResponseCode is None: # Any None field means the token was invalid. # This might happen if someone tampered with Token= itself in the # query string or if Bango's server was messed up. statsd.incr('solitude.bango.response.checktoken_fail') msg = 'Invalid Bango token: {0}'.format(tok) log.error(msg) raise forms.ValidationError(msg) for form_fld, true_attr in ( ('moz_signature', 'Signature'), ('moz_transaction', 'MerchantTransactionId'), ('bango_response_code', 'ResponseCode'), ('bango_response_message', 'ResponseMessage'), ('bango_trans_id', 'BangoTransactionId'), ): true_val = getattr(true_data, true_attr) # Make sure the true value is a str() just like it is on the query # string. true_val = str(true_val) form_val = cleaned_data.get(form_fld) # Since moz_transaction is an object, get the real value. if form_val and form_fld == 'moz_transaction': form_val = form_val.uuid if form_val and form_val != true_val: msg = ('Bango query string tampered with: field: {field}; ' 'fake: {fake}; true: {true}'.format(field=form_fld, fake=form_val, true=true_val)) log_cef(msg, self._request, severity=3) log.info(msg) log.info('token check response: {true_data}'.format( true_data=true_data)) # Completely reject the form since it was tampered with. raise forms.ValidationError( 'Form field {0} has been tampered with. ' 'True: {1}; fake: {2}'.format(form_fld, true_val, form_val))
def _check_for_tampering(self, tok, cleaned_data): """ Use the token service to see if any data has been tampered with. """ cli = get_client().client('token_checker') with statsd.timer('solitude.bango.request.checktoken'): true_data = cli.service.CheckToken(token=tok) if true_data.ResponseCode is None: # Any None field means the token was invalid. # This might happen if someone tampered with Token= itself in the # query string or if Bango's server was messed up. statsd.incr('solitude.bango.response.checktoken_fail') msg = 'Invalid Bango token: {0}'.format(tok) log.error(msg) raise forms.ValidationError(msg) for form_fld, true_attr in ( ('moz_signature', 'Signature'), ('moz_transaction', 'MerchantTransactionId'), ('bango_response_code', 'ResponseCode'), ('bango_response_message', 'ResponseMessage'), ('bango_trans_id', 'BangoTransactionId'),): true_val = getattr(true_data, true_attr) # Make sure the true value is a str() just like it is on the query # string. true_val = str(true_val) form_val = cleaned_data.get(form_fld) # Since moz_transaction is an object, get the real value. if form_val and form_fld == 'moz_transaction': form_val = form_val.uuid if form_val and form_val != true_val: msg = ('Bango query string tampered with: field: {field}; ' 'fake: {fake}; true: {true}' .format(field=form_fld, fake=form_val, true=true_val)) log_cef(msg, self._request, severity=3) log.info(msg) log.info('token check response: {true_data}' .format(true_data=true_data)) # Completely reject the form since it was tampered with. raise forms.ValidationError( 'Form field {0} has been tampered with. ' 'True: {1}; fake: {2}'.format( form_fld, true_val, form_val))
def client(self, method, data, raise_on=None, client=None): """ Client to call the bango client and process errors in a way that is relevant to the form. If you pass in a list of errors, these will be treated as errors the callee is going to deal with and will not be returning ImmediateHttpResponses. Instead the callee will have to cope with these BangoAnticipatedErrors as appropriate. You can optionally pass in a client to override the default. """ raise_on = raise_on or [] try: return getattr(client or get_client(), method)(data) except BangoUnanticipatedError, exc: # It was requested that the error that was passed in # was actually anticipated, so let's raise that type of error. if exc.id in raise_on: raise BangoAnticipatedError(exc.id, exc.message) res = self.client_errors(exc) raise BangoImmediateError(format_form_errors(res))
def prepare(form, bango): data = form.bango_data # Add in the Bango number from the serializer. data['bango'] = bango # Used to create the approprate data structure. client = get_client() billing = client.client('billing') price_list = billing.factory.create('ArrayOfPrice') price_types = set() for item in form.cleaned_data['prices']: price = billing.factory.create('Price') price.amount = item.cleaned_data['price'] price.currency = item.cleaned_data['currency'] price_types.add(item.cleaned_data['method']) # TODO: remove this. # Very temporary and very fragile hack to fix bug 882183. # Bango cannot accept regions with price info so if there # are two USD values for different regions it triggers a 500 error. append = True for existing in price_list.Price: if existing.currency == price.currency: log.info('Skipping %s:%s because we already have %s:%s' % (price.currency, price.amount, existing.currency, existing.amount)) append = False break if append: price_list.Price.append(price) data['priceList'] = price_list # More workarounds for bug 882321, ideally we'd send one type per # region, price, combination. If all the prices say operator, then # we'll set it to that. Otherwise its all. type_filters = PAYMENT_TYPES if price_types == set([str(PAYMENT_METHOD_OPERATOR)]): type_filters = MICRO_PAYMENT_TYPES types = billing.factory.create('ArrayOfString') for f in type_filters: types.string.append(f) data['typeFilter'] = types config = billing.factory.create('ArrayOfBillingConfigurationOption') configs = { 'APPLICATION_CATEGORY_ID': '18', 'APPLICATION_SIZE_KB': data.pop('application_size'), # Tell Bango to use our same transaction expiry logic. # However, we pad it by 60 seconds to show a prettier Mozilla user # error in the case of a real timeout. 'BILLING_CONFIGURATION_TIME_OUT': settings.TRANSACTION_EXPIRY + 60, 'REDIRECT_URL_ONSUCCESS': data.pop('redirect_url_onsuccess'), 'REDIRECT_URL_ONERROR': data.pop('redirect_url_onerror'), 'REQUEST_SIGNATURE': sign(data['externalTransactionId']), } user_uuid = data.pop('user_uuid') if settings.SEND_USER_ID_TO_BANGO: configs['MOZ_USER_ID'] = user_uuid log.info('Sending MOZ_USER_ID {uuid} for transaction {tr}' .format(uuid=user_uuid, tr=data['externalTransactionId'])) if settings.BANGO_ICON_URLS: icon_url = data.pop('icon_url', None) if icon_url: configs['APPLICATION_LOGO_URL'] = icon_url for k, v in configs.items(): opt = billing.factory.create('BillingConfigurationOption') opt.configurationOptionName = k opt.configurationOptionValue = v config.BillingConfigurationOption.append(opt) data['configurationOptions'] = config return data
def obj_create(self, bundle, request, **kwargs): form = CreateBillingConfigurationForm(bundle.data) if not form.is_valid(): raise self.form_errors(form) client = get_client() billing = client.client('billing') data = form.bango_data usd_price = None price_list = billing.factory.create('ArrayOfPrice') for item in form.cleaned_data['prices']: price = billing.factory.create('Price') price.amount = item.cleaned_data['amount'] price.currency = item.cleaned_data['currency'] if price.currency == 'USD': usd_price = Decimal(price.amount) price_list.Price.append(price) data['priceList'] = price_list if not usd_price: # This should never happen because USD is always part of the list. raise ValueError('Purchase for %r did not contain a USD price' % data.get('externalTransactionId')) if usd_price < settings.BANGO_MAX_MICRO_AMOUNT: type_filters = MICRO_PAYMENT_TYPES else: type_filters = PAYMENT_TYPES types = billing.factory.create('ArrayOfString') for f in type_filters: types.string.append(f) data['typeFilter'] = types config = billing.factory.create('ArrayOfBillingConfigurationOption') configs = { 'APPLICATION_CATEGORY_ID': '18', 'APPLICATION_SIZE_KB': 2, 'BILLING_CONFIGURATION_TIME_OUT': 120, 'REDIRECT_URL_ONSUCCESS': data.pop('redirect_url_onsuccess'), 'REDIRECT_URL_ONERROR': data.pop('redirect_url_onerror'), 'REQUEST_SIGNATURE': sign(data['externalTransactionId']), } if settings.BANGO_ICON_URLS: icon_url = data.pop('icon_url', None) if icon_url: configs['APPLICATION_LOGO_URL'] = icon_url for k, v in configs.items(): opt = billing.factory.create('BillingConfigurationOption') opt.configurationOptionName = k opt.configurationOptionValue = v config.BillingConfigurationOption.append(opt) data['configurationOptions'] = config resp = self.client('CreateBillingConfiguration', data) bundle.data = {'responseCode': resp.responseCode, 'responseMessage': resp.responseMessage, 'billingConfigurationId': resp.billingConfigurationId} create_data = data.copy() create_data['transaction_uuid'] = data.pop('externalTransactionId') create.send(sender=self, bundle=bundle, data=create_data, form=form) return bundle
def call(self, form): data = form.bango_data client = get_client() billing = client.client('billing') usd_price = None price_list = billing.factory.create('ArrayOfPrice') for item in form.cleaned_data['prices']: price = billing.factory.create('Price') price.amount = item.cleaned_data['price'] price.currency = item.cleaned_data['currency'] if price.currency == 'USD': usd_price = Decimal(price.amount) # TODO: remove this. # Very temporary and very fragile hack to fix bug 882183. # Bango cannot accept regions with price info so if there # are two USD values for different regions it triggers a 500 error. append = True for existing in price_list.Price: if existing.currency == price.currency: log.info('Skipping %s:%s because we already have %s:%s' % (price.currency, price.amount, existing.currency, existing.amount)) append = False break if append: price_list.Price.append(price) data['priceList'] = price_list if not usd_price: # This should never happen because USD is always part of the list. raise ValueError('Purchase for %r did not contain a USD price' % data.get('externalTransactionId')) if usd_price < settings.BANGO_MAX_MICRO_AMOUNT: type_filters = MICRO_PAYMENT_TYPES else: type_filters = PAYMENT_TYPES types = billing.factory.create('ArrayOfString') for f in type_filters: types.string.append(f) data['typeFilter'] = types config = billing.factory.create('ArrayOfBillingConfigurationOption') configs = { 'APPLICATION_CATEGORY_ID': '18', 'APPLICATION_SIZE_KB': data.pop('application_size'), # Tell Bango to use our same transaction expiry logic. # However, we pad it by 60 seconds to show a prettier Mozilla user # error in the case of a real timeout. 'BILLING_CONFIGURATION_TIME_OUT': settings.TRANSACTION_EXPIRY + 60, 'REDIRECT_URL_ONSUCCESS': data.pop('redirect_url_onsuccess'), 'REDIRECT_URL_ONERROR': data.pop('redirect_url_onerror'), 'REQUEST_SIGNATURE': sign(data['externalTransactionId']), } user_uuid = data.pop('user_uuid') if settings.SEND_USER_ID_TO_BANGO: configs['MOZ_USER_ID'] = user_uuid if settings.BANGO_ICON_URLS: icon_url = data.pop('icon_url', None) if icon_url: configs['APPLICATION_LOGO_URL'] = icon_url for k, v in configs.items(): opt = billing.factory.create('BillingConfigurationOption') opt.configurationOptionName = k opt.configurationOptionValue = v config.BillingConfigurationOption.append(opt) data['configurationOptions'] = config return self.client('CreateBillingConfiguration', data)
def client(self, method, data, client=None): return getattr(client or get_client(), method)(data)
def prepare(form, bango): data = form.bango_data # Add in the Bango number from the serializer. data['bango'] = bango # Used to create the approprate data structure. client = get_client() billing = client.client('billing') price_list = billing.factory.create('ArrayOfPrice') price_types = set() for item in form.cleaned_data['prices']: price = billing.factory.create('Price') price.amount = item.cleaned_data['price'] price.currency = item.cleaned_data['currency'] price_types.add(item.cleaned_data['method']) # TODO: remove this. # Very temporary and very fragile hack to fix bug 882183. # Bango cannot accept regions with price info so if there # are two USD values for different regions it triggers a 500 error. append = True for existing in price_list.Price: if existing.currency == price.currency: log.info('Skipping %s:%s because we already have %s:%s' % (price.currency, price.amount, existing.currency, existing.amount)) append = False break if append: price_list.Price.append(price) data['priceList'] = price_list # More workarounds for bug 882321, ideally we'd send one type per # region, price, combination. If all the prices say operator, then # we'll set it to that. Otherwise its all. type_filters = PAYMENT_TYPES if price_types == set([str(PAYMENT_METHOD_OPERATOR)]): type_filters = MICRO_PAYMENT_TYPES types = billing.factory.create('ArrayOfString') for f in type_filters: types.string.append(f) data['typeFilter'] = types config = billing.factory.create('ArrayOfBillingConfigurationOption') configs = { 'APPLICATION_CATEGORY_ID': '18', 'APPLICATION_SIZE_KB': data.pop('application_size'), # Tell Bango to use our same transaction expiry logic. # However, we pad it by 60 seconds to show a prettier Mozilla user # error in the case of a real timeout. 'BILLING_CONFIGURATION_TIME_OUT': settings.TRANSACTION_EXPIRY + 60, 'REDIRECT_URL_ONSUCCESS': data.pop('redirect_url_onsuccess'), 'REDIRECT_URL_ONERROR': data.pop('redirect_url_onerror'), 'REQUEST_SIGNATURE': sign(data['externalTransactionId']), } user_uuid = data.pop('user_uuid') if settings.SEND_USER_ID_TO_BANGO: configs['MOZ_USER_ID'] = user_uuid log.info('Sending MOZ_USER_ID {uuid} for transaction {tr}'.format( uuid=user_uuid, tr=data['externalTransactionId'])) if settings.BANGO_ICON_URLS: icon_url = data.pop('icon_url', None) if icon_url: configs['APPLICATION_LOGO_URL'] = icon_url for k, v in configs.items(): opt = billing.factory.create('BillingConfigurationOption') opt.configurationOptionName = k opt.configurationOptionValue = v config.BillingConfigurationOption.append(opt) data['configurationOptions'] = config return data