def post(self, request): uuid_or_smalluuid = request.data.get("id") if not uuid_or_smalluuid: raise serializers.ValidationError({"id": "Required"}) try: uuid = UUID(uuid_or_smalluuid) except (TypeError, ValueError): try: uuid = UUID(SmallUUID(uuid_or_smalluuid).hex_grouped) except (TypeError, ValueError): raise serializers.ValidationError( {"id": "Must be a uuid or smalluuid"}) try: item = StorageItem.objects.get(pk=uuid) except StorageItem.DoesNotExist: statsd.increment("turnout.storage.reset_failure_missing") raise Http404 if item.purged: logger.info(f"Purged file at {item.purged}. Redirecting.") return HttpResponseRedirect(item.purged_url) if not item.validate_token(str(item.token)): # Only refresh the token if it's expired item.refresh_token() trigger_notification(item) return Response(status=status.HTTP_201_CREATED)
def dispatch(self, request, *args, **kwargs): try: self.kwargs["pk"] = SmallUUID(kwargs["pk"]).hex_grouped kwargs["pk"] = SmallUUID(kwargs["pk"]).hex_grouped statsd.increment("turnout.storage.download_smalluuid_key_usage") except (TypeError, ValueError): pass return super().dispatch(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs): item = self.get_object() token = self.request.GET.get("token") if token and item.validate_token(token): logger.info(f"Valid token {item.pk}. Redirecting to file url.") if not item.first_download: item.first_download = now() item.save(update_fields=["first_download"]) return item.file.url logger.info(f"Invalid token {item.pk}. Redirecting to reset URL.") statsd.increment("turnout.storage.download_token_invalid") return item.reset_url
def create(self, validated_data: Dict[(str, Any)]) -> "Model": request = self.context.get("request") if request and "partner" in request.GET: try: partner = Client.objects.get(pk=request.GET["partner"]) except (Client.DoesNotExist, ValueError, TypeError): logger.info(f'Invalid partner {request.GET["partner"]}') statsd.increment("turnout.multi_tenant.unknown_partner_request") partner = Client.objects.first() else: partner = Client.objects.first() validated_data["partner"] = partner return super().create(validated_data)
def post(self, request): serializer = SmallUUIDKeySerializer(data=request.data) serializer.is_valid(raise_exception=True) try: item = StorageItem.objects.get(pk=serializer.validated_data["id"],) except StorageItem.DoesNotExist: statsd.increment("turnout.storage.reset_failure_missing") raise Http404 item.refresh_token() # TODO: Trigger email to user with new token return Response(status=status.HTTP_201_CREATED)
def clean_token(self): token = self.cleaned_data["token"] verify_is_allowed, extra = self.device.verify_is_allowed() if not verify_is_allowed: statsd.increment("turnout.multifactor.verification_not_allowed") statsd.increment("turnout.multifactor.verification_failure") # Try to match specific conditions we know about. if ( "reason" in extra and extra["reason"] == VerifyNotAllowed.N_FAILED_ATTEMPTS ): raise forms.ValidationError( "Verification temporarily disabled because of too many failed attempts, please try again soon." ) if "error_message" in extra: raise forms.ValidationError(extra["error_message"]) # Fallback to generic message otherwise. raise forms.ValidationError( "Verification of the token is currently disabled" ) verified = self.device.verify_token(token) if not verified: statsd.increment("turnout.multifactor.verification_failure") raise forms.ValidationError("Invalid token") statsd.increment("turnout.multifactor.verification_success")
def wrapper(request, *args, **kwargs): validator = RequestValidator(settings.TWILIO_AUTH_TOKEN) url = settings.PRIMARY_ORIGIN + request.get_full_path() if not validator.validate( url, request.data, request.headers.get("X-Twilio-Signature", ""), ): statsd.increment("turnout.smsbot.bad_twilio_signature") logger.warning( f"Bad twilio callback signature, url {url}, headers {request.headers}, request {request.data}" ) return HttpResponse("Bad twilio signature", status=status.HTTP_403_FORBIDDEN) return func(request, *args, **kwargs)
def send_sendgrid_mail(key): mail = cache.get(key) try: sg.client.mail.send.post(request_body=mail) except Exception: statsd.increment("turnout.mailer.sendgrid_send_task_server_error") raise finally: cache.delete(key) subject = mail["subject"] sent_from = mail["from"]["email"] sent_to = mail["personalizations"][0]["to"] logger.info(f"Email Sent {subject} from {sent_from} to {sent_to}") logger.debug(mail)
def create(self, validated_data: Dict[(str, Any)]) -> "Model": request = self.context.get("request") if request and "partner" in request.GET: try: partner_slug = PartnerSlug.objects.only("partner").get( slug=request.GET["partner"]) partner = partner_slug.partner except PartnerSlug.DoesNotExist: logger.info(f'Invalid partner {request.GET["partner"]}') statsd.increment( "turnout.multi_tenant.unknown_partner_request") partner = Client.objects.first() else: partner = Client.objects.first() validated_data["partner"] = partner return super().create(validated_data)
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) targetsmart_response = query_targetsmart(serializer.validated_data) if targetsmart_response.get("error"): statsd.increment("turnout.verifier.ts_error") logger.error(f"Targetsmart Error {targetsmart_response['error']}") return Response( {"error": "Error from data provider"}, status=status.HTTP_503_SERVICE_UNAVAILABLE, ) # set the state to a real model from the code serializer.validated_data["state"] = State.objects.get( code=serializer.validated_data["state"]) serializer.validated_data["response"] = targetsmart_response registered = False if targetsmart_response["result"]: # if result is true, there is a single matching record targetsmart_record = targetsmart_response["result_set"][0] if targetsmart_record["vb.vf_voter_status"] == "Active": serializer.validated_data[ "voter_status"] = enums.VoterStatus.ACTIVE registered = True elif targetsmart_record["vb.vf_voter_status"] == "Inactive": serializer.validated_data[ "voter_status"] = enums.VoterStatus.INACTIVE else: serializer.validated_data[ "voter_status"] = enums.VoterStatus.UNKNOWN statsd.increment("turnout.verifier.singleresult") serializer.validated_data["too_many"] = targetsmart_response[ "too_many"] serializer.validated_data["registered"] = registered instance = serializer.save() response = {"registered": registered, "action_id": instance.action.pk} if targetsmart_response["too_many"]: statsd.increment("turnout.verifier.toomany") if registered: statsd.increment("turnout.verifier.registered") return Response(response)
def geocode_to_regions(street, city, state, zipcode): addrs = geocode( street=street, city=city, state=state, zipcode=zipcode, fields="stateleg", ) if not addrs: logger.warning( f"address: Unable to geocode ({street}, {city}, {state} {zipcode})" ) statsd.increment("turnout.official.address.failed_geocode") return None # use the first/best match addr = addrs[0] county = addr["address_components"].get("county") if not county: return None city = addr["address_components"].get("city") location = Point(addr["location"]["lng"], addr["location"]["lat"]) state_code = addr["address_components"].get("state") if state_code != state: # if the address match gets the state wrong, return *no* result logger.warning( f"User-provided state {state} does not match geocoded state in {addr['address_components']}" ) return None return match_region( city, county, state, location=location, districts=addr.get("fields", {}).get("state_legislative_districts", {}), )
def send_sendgrid_mail(key): mail = cache.get(key) try: with tracer.trace("sg.mail.send", service="sendgridclient"): sg.client.mail.send.post(request_body=mail) except Exception: statsd.increment("turnout.mailer.sendgrid_send_task_server_error") raise finally: cache.delete(key) extra = { "subject": mail["subject"], "sent_from": mail["from"]["email"], "sent_to": [d["email"] for d in mail["personalizations"][0]["to"]], } logger.info("Email Sent %(subject)s from %(sent_from)s to %(sent_to)s", extra, extra=extra) logger.debug(mail)
def request_subscriber(self) -> Client: if self._request_subscriber: return self._request_subscriber request = self.context.get("request") if request and "subscriber" in request.GET: try: subscriber_slug = SubscriberSlug.objects.only("subscriber").get( slug=request.GET["subscriber"], subscriber__status=enums.SubscriberStatus.ACTIVE, ) subscriber = subscriber_slug.subscriber except SubscriberSlug.DoesNotExist: logger.info(f'Invalid subscriber {request.GET["subscriber"]}') statsd.increment("turnout.multi_tenant.unknown_subscriber_request") subscriber = Client.objects.first() else: subscriber = Client.objects.first() self._request_subscriber = subscriber return subscriber
def get_redirect_url(self, *args, **kwargs): item = self.get_object() token = self.request.GET.get("token") statsd.distribution( "turnout.storage.token_validity_seconds", time.time() - item.expires.timestamp(), ) if item.purged: logger.info(f"Purged file at {item.purged}. Redirecting.") return item.purged_url if token and item.validate_token(token): logger.info(f"Valid token {item.pk}. Redirecting to file url.") try: item.track_download() except Exception: logger.exception(f"Unable to track download of {item.pk}") return item.file.url logger.info(f"Invalid token {item.pk}. Redirecting to reset URL.") statsd.increment("turnout.storage.download_token_invalid") return item.reset_url
def get_regions_for_address(street, city, state, zipcode): state = state.upper() if state else state regions = [] # avoid TS for "easy" addresses, since the geocod.io latency (esp # long tail) *seems* to be better. Also specifically avoid it for AL and AK, # where we do matches based on lat/lng or state leg district. TS # works well other "problem" states, though! # avoid TS for MA too since I've seen at least one problematic address # on a city boundary for which TS returns the wrong municipality/region, whereas # geocod.io gets it right. # a few states do better with targetsmart. for the others, we get # better results from geocodio. prefer_ts = ["VT", "MI"] if state not in prefer_ts: regions = geocode_to_regions(street, city, state, zipcode) if not regions or len(regions) != 1: # try targetsmart r = ts_to_region(street, city, state, zipcode) if r and len(r) == 1: regions = r if not regions and state in prefer_ts: regions = geocode_to_regions(street, city, state, zipcode) if not regions: logger.warning( f"address: No match for ({street}, {city}, {state} {zipcode})") statsd.increment("turnout.official.address.no_region_match") elif len(regions) > 1: logger.warning( f"address: Multiple results for ({street}, {city}, {state} {zipcode}): {regions}" ) statsd.increment("turnout.official.address.multiple_region_matches") return (regions, False)
def wrapper(request, *args, **kwargs): import hashlib import hmac timestamp = request.headers.get("Lob-Signature-Timestamp", "") message = timestamp + "." + request.body.decode("utf-8") secret = settings.LOB_LETTER_WEBHOOK_SECRET if not secret: return HttpResponse("Bad lob signature", status=status.HTTP_403_FORBIDDEN) signature = hmac.new( secret.encode("utf-8"), message.encode("utf-8"), digestmod=hashlib.sha256, ).hexdigest() if signature != request.headers.get("Lob-Signature"): statsd.increment("turnout.integration.bad_lob_signature") logger.warning( f"Bad lob signature, headers {request.headers}, data {request.data}" ) return HttpResponse("Bad lob signature", status=status.HTTP_403_FORBIDDEN) return func(request, *args, **kwargs)
def post(self, request): print(request.data) serializer = GatewayCallbackSerializer(data=request.data) serializer.is_valid(raise_exception=True) fax_id = serializer.validated_data["fax_id"] try: fax: Fax = Fax.objects.get(pk=fax_id) except Fax.DoesNotExist: statsd.increment("turnout.fax.callback.fax_does_not_exist") logger.warn(f"Got fax callback with nonexistant fax ID: {fax_id}") return Response({"error": "No such fax"}, status=status.HTTP_404_NOT_FOUND) token = request.GET.get("token") if not token: statsd.increment("turnout.fax.callback.no_token") logger.warn(f"Got fax callback with missing token for fax ID: {fax_id}") return Response( {"error": "Missing token"}, status=status.HTTP_400_BAD_REQUEST ) if not fax.validate_token(token): statsd.increment("turnout.fax.callback.wrong_token") logger.warn(f"Got fax callback with wrong token for fax ID: {fax_id}") return Response( {"error": "Token is incorrect"}, status=status.HTTP_403_FORBIDDEN ) if fax.status_timestamp and ( serializer.validated_data["timestamp"] < fax.status_timestamp ): statsd.increment("turnout.fax.callback.outdated_timestamp") logger.warn( f"Got fax callback with outdated timestamp {serializer.validated_data['timestamp']} for fax ID: {fax_id}" ) return Response( {"warning": "Outdated timestamp; ignoring"}, status=status.HTTP_200_OK ) handle_fax_callback(serializer.validated_data, fax) return Response({"ok": True}, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) state_code = serializer.validated_data["state"] with tracer.trace("verifier.apicalls"): alloy = gevent.spawn(query_alloy, serializer.validated_data) targetsmart = gevent.spawn(query_targetsmart, serializer.validated_data) gevent.joinall([alloy, targetsmart]) serializer.validated_data["targetsmart_response"] = targetsmart.value serializer.validated_data["alloy_response"] = alloy.value if alloy.value.get("error"): statsd.increment("turnout.verifier.alloy_error") logger.error(f"Alloy Error {alloy.value['error']}") if targetsmart.value.get("error"): statsd.increment("turnout.verifier.ts_error") logger.error(f"Targetsmart Error {targetsmart.value['error']}") if targetsmart.value.get("error") and alloy.value.get("error"): return Response( {"error": "Error from data providers"}, status=status.HTTP_503_SERVICE_UNAVAILABLE, ) alloy_record = alloy.value.get("data", {}) if len(targetsmart.value.get("result_set", [])) == 1: targetsmart_record = targetsmart.value.get("result_set")[0] else: targetsmart_record = {} targetsmart_status = enums.VoterStatus.UNKNOWN if targetsmart_record.get("vb.vf_voter_status") == "Inactive": targetsmart_status = enums.VoterStatus.INACTIVE elif targetsmart_record.get("vb.vf_voter_status") == "Active": targetsmart_status = enums.VoterStatus.ACTIVE alloy_status = enums.VoterStatus.UNKNOWN if alloy_record.get("registration_status") == "Inactive": alloy_status = enums.VoterStatus.INACTIVE elif alloy_record.get("registration_status") == "Active": alloy_status = enums.VoterStatus.ACTIVE registered = False final_status = enums.VoterStatus.UNKNOWN if (alloy_status == enums.VoterStatus.INACTIVE or targetsmart_status == enums.VoterStatus.INACTIVE): final_status = enums.VoterStatus.INACTIVE elif (alloy_status == enums.VoterStatus.ACTIVE or targetsmart_status == enums.VoterStatus.ACTIVE): final_status = enums.VoterStatus.ACTIVE registered = True serializer.validated_data["targetsmart_status"] = targetsmart_status serializer.validated_data["alloy_status"] = alloy_status serializer.validated_data["voter_status"] = final_status serializer.validated_data["registered"] = registered if alloy_status != targetsmart_status: statsd.increment("turnout.verifier.vendor_mismatch", tags=[f"state:{state_code}"]) serializer.validated_data["state"] = State.objects.get(code=state_code) instance = serializer.save() instance.action.track_event(EventType.FINISH) action_finish.delay(instance.action.pk) response = {"registered": registered, "action_id": instance.action.pk} if registered: statsd.increment("turnout.verifier.registered", tags=[f"state:{state_code}"]) else: statsd.increment("turnout.verifier.unregistered", tags=[f"state:{state_code}"]) return Response(response)