示例#1
0
    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)
示例#2
0
 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)
示例#3
0
 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)
示例#5
0
    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)
示例#6
0
    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")
示例#7
0
 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)
示例#8
0
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)
示例#9
0
    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)
示例#10
0
    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)
示例#11
0
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",
                                             {}),
    )
示例#12
0
文件: tasks.py 项目: liewegas/turnout
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)
示例#13
0
    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
示例#14
0
    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
示例#15
0
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)
示例#16
0
    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)
示例#17
0
    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)
示例#18
0
    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)