def post(self, request, format=None): """POST handler.""" from_ = str(request.POST['from']) to_ = str(request.POST['to']) body = str(request.POST['body']) network = get_network_from_user(request.user) if network.billing_enabled and network.ledger.balance <= 0: # Shouldn't this be a 402 payment required? -kurtis return Response("operator has no credit in Endaga account", status=status.HTTP_400_BAD_REQUEST) q = models.Number.objects.get(number=from_) if (q.kind not in SendSMS.HANDLERS): #shouldn't this be a 404 not found? -kurtis return Response("Invalid sending number", status=status.HTTP_400_BAD_REQUEST) else: provider = SendSMS.HANDLERS[q.kind][0]( SendSMS.HANDLERS[q.kind][1], #username SendSMS.HANDLERS[q.kind][2], #password SendSMS.HANDLERS[q.kind][3], #inbound_sms SendSMS.HANDLERS[q.kind][4], #outbound_sms SendSMS.HANDLERS[q.kind][5]) #inbound_voice try: provider.send(to_, from_, body) except Exception: message = '%s to: %s, from: %s, body len: %s' % ( provider, to_, from_, len(body)) raise Exception(message) # Bill the operator. cost_to_operator = network.calculate_operator_cost( 'off_network_send', 'sms', destination_number=to_) network.bill_for_sms(cost_to_operator, 'outside_sms') return Response("", status=status.HTTP_202_ACCEPTED)
def delete(self, request, tower_uuid): network = get_network_from_user(request.user) tower = models.BTS.objects.get(uuid=tower_uuid) if tower.network and tower.network != network: return Response("Network is not associated with that BTS.", status=status.HTTP_403_FORBIDDEN) # Create a DerigisteredBTS instance. dbts = models.DeregisteredBTS(uuid=tower.uuid, secret=tower.secret) dbts.save() # Create a 'deregister_bts' UsageEvent. now = datetime.datetime.now(pytz.utc) if tower.nickname: name = 'tower "%s" (%s)' % (tower.nickname, tower.uuid) else: name = 'tower %s' % tower.uuid event = models.UsageEvent.objects.create(date=now, bts_uuid=tower.uuid, kind='deregister_bts', reason='deregistered %s' % name) event.save() # TODO(matt): generate revocation certs # And finally delete the BTS. tower.delete() return Response("")
def post(self, request, format=None): """ Request a number and associate with a subscriber. """ if not ("bts_uuid" in request.POST or "imsi" in request.POST): return Response("", status=status.HTTP_400_BAD_REQUEST) bts_uuid = str(request.POST['bts_uuid']) imsi = str(request.POST['imsi']) network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("User is not associated with that BTS.", status=status.HTTP_403_FORBIDDEN) # If the IMSI is already in use, and associated with another network, # prevent the registration of a new number. If it's associated with # this network, simply return the currently-associated number. N.B., # this effectively enforces a 1-1 mapping of subscriber to number # currently. try: subscriber = models.Subscriber.objects.get(imsi=imsi) if subscriber.network != network: return Response("IMSI already registered to another network", status=status.HTTP_409_CONFLICT) except models.Subscriber.DoesNotExist: # Create a new subscriber if one matching this IMSI didn't already # exist. subscriber = models.Subscriber(network=network, imsi=imsi, balance=0, bts=bts) subscriber.save() # If the subscriber already exists, we should return the associated # phone number and update the BTS to match what is being used. n = models.Number.objects.filter(subscriber=subscriber).first() if not n: # Otherwise, pick a random available number and associate it. with transaction.atomic(): n = models.Number.objects.filter( state="available", country_id=network.number_country).first() if not n: return Response("No number available", status=status.HTTP_404_NOT_FOUND) n.state = "inuse" n.network = network n.subscriber = subscriber n.save() n.charge() return Response( { 'number': n.number, 'subscriber': subscriber.imsi, 'balance': subscriber.balance }, status=status.HTTP_200_OK)
def delete(self, request, imsi): network = get_network_from_user(request.user) subscriber = models.Subscriber.objects.get(imsi=imsi) if subscriber.network != network: return Response("Network is not associated with that Subscriber.", status=status.HTTP_403_FORBIDDEN) # This is a valid request, begin processing. subscriber.deactivate() return Response("")
def get(self, request, format=None): """ Update the current inbound URL, and return registration status. """ if not all( [_ in request.GET for _ in ['bts_uuid', 'vpn_status', 'vpn_ip']]): return Response(status=status.HTTP_400_BAD_REQUEST) bts_uuid = request.GET['bts_uuid'] vpn_up = request.GET['vpn_status'] == "up" vpn_ip = request.GET['vpn_ip'] # Old versions of client didn't specify this, but always ran on port # 8081, so assume that as default. federer_port = request.GET.get('federer_port', "8081") network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response({"status": "BTS isn't registered."}, status=status.HTTP_403_FORBIDDEN) # we know the bts has the right token if it's authed, so provide the secret # TODO: cycle this to a random code if not bts.secret: bts.secret = bts.uuid bts.save() if bts.is_registered() and vpn_up: inbound = self.update_inbound_url(bts, vpn_ip, federer_port) bts.save() return Response( { 'status': 'registered, ok', 'inbound': inbound, 'bts_secret': bts.secret }, status=status.HTTP_200_OK) elif vpn_up: # the BTS reports the VPN is up, and has authed w/ API key, #so we consider the BTS fully reigstered. bts.mark_registered() bts.mark_active() inbound = self.update_inbound_url(bts, vpn_ip, federer_port) bts.save() return Response( { 'status': 'unregistered -> registered', 'inbound': inbound, 'bts_secret': bts.secret }, status=status.HTTP_200_OK) else: # TODO: add logic to handle lack of inbound URL return Response("", status=status.HTTP_404_NOT_FOUND)
def get(self, request, bts_uuid=None, number=None, format=None): """ Associate a number to a BTS. DEPRECATED (shasan 2016jan5) -- use the POST endpoint instead """ if not (number or bts_uuid or "imsi" in request.GET): return Response("", status=status.HTTP_400_BAD_REQUEST) network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("User is not associated with that BTS.", status=status.HTTP_403_FORBIDDEN) # If the IMSI is already in use, and associated with another BTS, # prevent the registration of a new number. However, we allow IMSIs # to register a second number on the IMSI's original BTS. imsi = request.GET['imsi'] try: subscriber = models.Subscriber.objects.get(imsi=imsi) if subscriber.network != network: return Response("IMSI already registered", status=status.HTTP_409_CONFLICT) except models.Subscriber.DoesNotExist: # Create a new subscriber if one matching this IMSI didn't already # exist. subscriber = models.Subscriber(network=network, imsi=imsi, balance=0, bts=bts) subscriber.save() with transaction.atomic(): q = models.Number.objects.filter(number__exact="%s" % number) if len(q) > 0: n = q[0] # This is tricky. Numbers that get marked 'pending' will have # the network id already set, so this check fails and we set # the number as in-use. This is an artifact of the two-step # number registration process. So don't remove the network ID # check! if n.state != "available" and n.network != bts.network: return Response("Number already in use.", status=status.HTTP_400_BAD_REQUEST) n.network = bts.network n.state = "inuse" else: # FIXME this should never happen -- all numbers should already # be in the system, unless we're associating an old BTS for the # first time (like w/ Bok) n = models.Number(number=number, state="inuse", network=bts.network) # Associate it with the subscriber and save. n.subscriber = subscriber n.save() return Response(None, status=status.HTTP_200_OK)
def get(self, request): if not "number" in request.GET: return Response("Missing number.", status=status.HTTP_400_BAD_REQUEST) query_number = request.GET["number"] try: network = get_network_from_user(request.user) Number.objects.get(number=query_number, network=network) return Response("OK", status=status.HTTP_200_OK) except Number.DoesNotExist: return Response("Unauthorized", status=status.HTTP_401_UNAUTHORIZED)
def get(self, request, bts_uuid=None, format=None): """Return a number that's usable by a BTS. We first check for "available" numbers in our database. If there are none, we buy a number from a provider, set it up, and return here. We have to specify a specific BTS to avoid a race condition when multiple BTS register for a number at once. """ if not bts_uuid: return Response("No BTS UUID specified.", status=status.HTTP_400_BAD_REQUEST) network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("The specified BTS does not belong to the user.", status=status.HTTP_403_FORBIDDEN) # First check for available numbers. If a number is available, it's up # for grabs by anyone. with transaction.atomic(): q = models.Number.objects.filter(state__exact="available") for n in q: # We do this here rather than in the db query since at this # time some numbers don't have a country field to query on. Can # probably be removed later. -- SH (2014 aug 21) # TODO(matt): this potentially sets a lot of numbers as # pending.. if n.country() == network.number_country: n.state = "pending" n.network = network n.save() n.charge() return Response(int(n.number), status=status.HTTP_200_OK) # No number available, so we have to buy one from Nexmo. # TODO: Try to buy from multiple vendors np = NexmoProvider( settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, #outbound_sms_url settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST'], country=network.number_country) try: # This call creates the new number in the DB as a side effect. new_number = np.get_number(bts.network) print "New number is %s" % new_number return Response(new_number, status=status.HTTP_200_OK) except ValueError: return Response("Number not available", status=status.HTTP_404_NOT_FOUND)
def post(self, request): """Handles POST requests.""" try: bts_uuid, bts_status = self._process_post_data(request) except HTTP_415_Error as e: return Response(str(e), status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE) except HTTP_422_Error as e: return Response(str(e), status=status.HTTP_422_UNPROCESSABLE_ENTITY) except (ValueError, KeyError): return Response("Invalid/missing checkin parameters.", status=status.HTTP_400_BAD_REQUEST) # See if this BTS has been deregistered. try: dbts = models.DeregisteredBTS.objects.get(uuid=bts_uuid) resp = { 'status': 'deregistered', } dbts.delete() return Response({'response': resp}, status=status.HTTP_200_OK) except models.DeregisteredBTS.DoesNotExist: pass # The BTS isn't deregistered, continue with the checkin as normal. network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("Incorrect auth for BTS.", status=status.HTTP_403_FORBIDDEN) try: resp = checkin.CheckinResponder(bts).process(bts_status) except Exception as e: print "Error handling checkin (BTS %s): %s" % (bts.uuid, e) print "BTS status: %s" % bts_status raise checkin_resp = Response({'response': resp}, status=status.HTTP_200_OK) def gzip_response_callback(response): if len(response.content) < MIN_COMPRESSIBLE_RESPONSE_SZ: return response gzipped_resp = gzip_middleware.process_response(request, response) return gzipped_resp checkin_resp.add_post_render_callback(gzip_response_callback) return checkin_resp
def delete(self, request, bts_uuid=None, number=None, format=None): """ Dis-associate a number from a BTS and mark it available. """ if not (number or bts_uuid): return Response("", status=status.HTTP_400_BAD_REQUEST) network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response("User is not associated with that BTS.", status=status.HTTP_403_FORBIDDEN) with transaction.atomic(): q = models.Number.objects.filter(number__exact=number).filter( network=bts.network) for number in q: number.state = "available" number.network = None number.subscriber = None number.save() return Response(None, status=status.HTTP_200_OK) return Response(None, status=status.HTTP_404_NOT_FOUND)
def get(self, request): logger.warning("Use of deprecated API call %s" % (request.GET, )) if "uuid" not in request.GET: return Response("No uuid specified.", status=status.HTTP_400_BAD_REQUEST) query_num = request.GET["uuid"] try: network = get_network_from_user(request.user) bts = BTS.objects.get(uuid=query_num) # Strip the protocol field and just return the rest, removing any # trailing slash. bts_info = urlparse.urlparse(bts.inbound_url) result = { 'netloc': bts_info.netloc, 'hostname': bts_info.hostname, 'owner': bts.network.id } return Response(result, status=status.HTTP_200_OK) except Number.DoesNotExist: return Response("No such UUID", status=status.HTTP_404_NOT_FOUND)
def post(self, request, format=None): """ Submit a CSR for signing, and get back a signed cert as well as an OpenVPN conf. """ if not all([_ in request.POST for _ in ['bts_uuid', 'csr']]): return Response(status=status.HTTP_400_BAD_REQUEST) bts_uuid = request.POST['bts_uuid'] csr = request.POST['csr'] network = get_network_from_user(request.user) try: bts = models.BTS.objects.get(uuid=bts_uuid, network=network) except models.BTS.DoesNotExist: return Response({"status": "BTS isn't registered."}, status=status.HTTP_403_FORBIDDEN) r = requests.post('http://%s/csr' % settings.ENDAGA['KEYMASTER'], data={ 'ident': bts_uuid, 'csr': csr }) if r.status_code == 200: cert = json.loads(r.content)['certificate'] bts = models.BTS.objects.get(uuid=bts_uuid) bts.certificate = cert bts.save() vpnconf = self.get_vpn_conf(bts) return Response({ 'certificate': cert, 'vpnconf': vpnconf }, status=status.HTTP_200_OK) else: return Response("status: %d" % (r.status_code, ), status=status.HTTP_400_BAD_REQUEST)
def post(self, request, format=None): required_fields = ['msgid', 'log_name'] network = get_network_from_user(request.user) if not all([_ in request.POST for _ in required_fields]): return Response("Missing fields", status=status.HTTP_400_BAD_REQUEST) if not 'file' in request.FILES: return Response("Missing file", status=status.HTTP_400_BAD_REQUEST) try: log_req = models.BTSLogfile.objects.get(uuid=request.POST['msgid']) except models.BTSLogfile.DoesNotExist: return Response("Unknown request.", status=status.HTTP_400_BAD_REQUEST) if log_req.bts.network != network: return Response("Unauthorized.", status=status.HTTP_403_FORBIDDEN) log_req.logfile = request.FILES['file'] log_req.status = 'done' log_req.save() return Response("OK", status=status.HTTP_200_OK)
def post(self, request, msisdn): network = get_network_from_user(request.user) number = models.Number.objects.get(number=msisdn) if (number.network and number.network != network and not request.user.is_staff): return Response("User is not associated with that Number %s %s." % (number.network.pk, network.pk), status=status.HTTP_403_FORBIDDEN) # Must post a valid 'state'. valid_states = ('available', 'released') if request.POST.get('state', None) not in valid_states: return Response("Must post a valid state.", status=status.HTTP_400_BAD_REQUEST) # This is a valid request, begin processing. First check if this is a # number-deactivation request. if (number.state == 'inuse' and request.POST.get('state') == 'available'): # Refuse to deactivate a subscriber's last number. if (len(models.Number.objects.filter(subscriber=number.subscriber)) <= 1): message = ("Cannot deactivate a subscriber's last number." " Instead, delete the subscriber.") return Response(message, status=status.HTTP_400_BAD_REQUEST) # If it's not the subscriber's only number, send an async post to # the BTS to deactivate the number. Sign the request using JWT. bts = number.subscriber.bts url = '%s/config/deactivate_number' % bts.inbound_url data = { 'number': msisdn, # Add a UUID as a nonce for the message. 'msgid': str(uuid.uuid4()), } serializer = itsdangerous.JSONWebSignatureSerializer(bts.secret) signed_data = { 'jwt': serializer.dumps(data), } tasks.async_post.delay(url, signed_data) # Create a 'deactivate_number' UsageEvent. now = datetime.datetime.now(pytz.utc) reason = 'deactivated phone number: %s' % number.number event = models.UsageEvent.objects.create( subscriber=number.subscriber, date=now, bts=bts, kind='deactivate_number', to_number=number.number, reason=reason, oldamt=number.subscriber.balance, newamt=number.subscriber.balance, change=0) event.save() # Diassociate the Number from its former Subscriber and Network. number.subscriber = None number.network = None number.state = request.POST.get('state') number.save() return Response("") # Check if this is a number-release request. if request.POST.get('state') == 'released': # User must be staff to do this. if not request.user.is_staff: return Response("", status=status.HTTP_404_NOT_FOUND) # The number must not be 'inuse.' if number.state == 'inuse': return Response("", status=status.HTTP_400_BAD_REQUEST) # The number cannot be associated with a Sub. if number.subscriber: return Response("", status=status.HTTP_400_BAD_REQUEST) # Validation passes, release (cancel) the number. nexmo_provider = NexmoProvider( settings.ENDAGA['NEXMO_ACCT_SID'], settings.ENDAGA['NEXMO_AUTH_TOKEN'], settings.ENDAGA['NEXMO_INBOUND_SMS_URL'], None, settings.ENDAGA['NEXMO_INBOUND_VOICE_HOST'], country=number.country_id) if nexmo_provider.cancel_number(number.number): # Success, delete the number. number.delete() return Response("", status=status.HTTP_200_OK) else: print 'deleting number %s failed' % number.number return Response("", status=status.HTTP_500_INTERNAL_SERVER_ERROR) # Invalid request. return Response("", status=status.HTTP_400_BAD_REQUEST)