def save(self, *args, **kwargs):
        cutoff = timezone.now() - timedelta(
            minutes=settings.PETTYCASH_TERMS_MINS_CUTOFF)

        # Drop anything that is too old; and only keep the most recent up to
        # a cap -- feeble attempt at foiling obvious DOS. Mainly as the tags
        # can be as short as 32 bits and we did not want to also add a shared
        # scecret in the Arduino code. As these easily get committed to github
        # by accident.
        stale = (PettycashTerminal.objects.all().filter(
            Q(accepted=False)).order_by("date"))
        if len(stale) > settings.PETTYCASH_TERMS_MAX_UNKNOWN:
            lst = (User.objects.all().filter(
                groups__name=settings.PETTYCASH_ADMIN_GROUP).values_list(
                    "email", flat=True))
            emailPlain(
                "pettycash-dos-warn.txt",
                toinform=lst,
                context={
                    "base": settings.BASE,
                    "settings": settings,
                    "stale": stale
                },
            )
            logger.info("DOS mail set about too many terminals in waiting.")
        todel = set()
        for s in stale.filter(Q(date__lt=cutoff)):
            todel.add(s)
        for s in stale[settings.PETTYCASH_TERMS_MIN_UNKNOWN:]:
            todel.add(s)
        for s in todel:
            s.delete()

        return super(PettycashTerminal, self).save(*args, **kwargs)
Esempio n. 2
0
def sendEmail(transactions,
              balance,
              user,
              toinform,
              template="balance-email.txt"):
    gs = False
    topup = 0
    if balance.balance > Money(0, EUR):
        gs = True
    else:
        topup = (int(
            (-float(balance.balance.amount) + settings.PETTYCASH_TOPUP) / 5 +
            0.5) * 5)
    return emailPlain(
        template,
        toinform=toinform,
        context={
            "base": settings.BASE,
            "balance": balance,
            "date": datetime.now(tz=timezone.utc),
            "transactions": transactions,
            "goodstanding": gs,
            "topup": topup,
            "user": user,
        },
    )
Esempio n. 3
0
def reimburseform(request):
    form = PettycashReimbursementRequestForm(
        request.POST or None,
        request.FILES or None,
        initial={
            "dst": request.user,
            "date": date.today(),
        },
    )
    context = {
        "settings": settings,
        "form": form,
        "label": "Reimburse",
        "action": "request",
        "user": request.user,
        "has_permission": request.user.is_authenticated,
    }

    if form.is_valid():
        print(request.POST)
        item = form.save(commit=False)
        if not item.date:
            item.date = datetime.now()
        if not item.submitted:
            item.submitted = datetime.now()

        item.save()
        context["item"] = item

        attachments = []
        if item.scan:
            attachments.append(image2mime(item.scan))

        emailPlain(
            "email_imbursement_notify.txt",
            toinform=[pettycash_treasurer_emails(), request.user.email],
            context=context,
            attachments=attachments,
        )

        return render(request, "pettycash/reimburse_ok.html", context=context)

    return render(request, "pettycash/reimburse_form.html", context=context)
Esempio n. 4
0
def alertOwnersToChange(
    tx, userThatMadeTheChange=None, toinform=[], reason=None, template="email_tx.txt"
):
    src_label = "%s" % tx.src
    dst_label = "%s" % tx.dst
    label = dst_label

    if tx.src.id == settings.POT_ID:
        src_label = settings.POT_LABEL
    else:
        toinform.append(tx.src.email)

    if tx.dst.id == settings.POT_ID:
        dst_label = settings.POT_LABEL
        label = src_label
    else:
        toinform.append(tx.dst.email)

    if userThatMadeTheChange.email not in toinform:
        toinform.append(userThatMadeTheChange.email)

    # if settings.ALSO_INFORM_EMAIL_ADDRESSES:
    #    toinform.extend(settings.ALSO_INFORM_EMAIL_ADDRESSES)

    return emailPlain(
        template,
        toinform=toinform,
        context={
            "user": userThatMadeTheChange,
            "base": settings.BASE,
            "reason": reason,
            "tx": tx,
            "src_label": src_label,
            "dst_label": dst_label,
            "label": label,
            "settings": settings,
        },
    )
Esempio n. 5
0
def reimburseque(request):
    if not request.user.is_anonymous and request.user.can_escalate_to_priveleged:
        if not request.user.is_privileged:
            return redirect("sudo")
    if not request.user.is_privileged:
        return HttpResponse("XS denied", status=401, content_type="text/plain")

    if not request.user.groups.filter(name=settings.PETTYCASH_TREASURER_GROUP).exists():
        return HttpResponse("XS denied", status=401, content_type="text/plain")

    context = {
        "settings": settings,
        "label": "Reimburse",
        "action": "request",
        "user": request.user,
        "has_permission": request.user.is_authenticated,
    }
    form = PettycashReimburseHandleForm(request.POST or None)
    if form.is_valid():
        pk = form.cleaned_data["pk"]
        reason = form.cleaned_data["reason"].rstrip()
        if reason:
            reason = reason + "\n"

        approved = False
        if "approved" in (request.POST["submit"]):
            approved = True
        try:
            item = PettycashReimbursementRequest.objects.get(id=pk)
            context["item"] = item
            context["approved"] = approved
            context["reason"] = reason

            attachments = []
            if item.scan:
                attachments.append(image2mime(item.scan))

            if approved:
                context["reason"] = "Approved by %s (%d)" % (request.user, item.pk)
                if item.viaTheBank:
                    emailPlain(
                        "email_imbursement_bank_approved.txt",
                        toinform=[pettycash_treasurer_emails(), request.user.email],
                        context=context,
                        attachments=attachments,
                    )
                else:
                    transact_raw(
                        request,
                        src=User.objects.get(id=settings.POT_ID),
                        dst=item.dst,
                        description=item.description,
                        amount=item.amount,
                        reason=context["reason"],
                        user=request.user,
                    )
            else:
                context["reason"] = "Rejected by %s (%d)" % (request.user, item.pk)
                emailPlain(
                    "email_imbursement_rejected.txt",
                    toinform=[pettycash_treasurer_emails(), request.user.email],
                    context=context,
                    attachments=attachments,
                )

            item._change_reason = context["reason"]
            item.delete()

            return redirect(reverse("reimburse_queue"))

        except ObjectDoesNotExist as e:
            logger.error("Reimbursment %d not found" % (pk))
            return HttpResponse(
                "Reimbursement not found", status=404, content_type="text/plain"
            )

    items = []
    for tx in PettycashReimbursementRequest.objects.all().order_by("submitted"):
        item = {}
        item["tx"] = tx
        item["form"] = PettycashReimburseHandleForm(initial={"pk": tx.pk})
        item["action"] = "foo"
        items.append(item)

    context["items"] = items
    return render(request, "pettycash/reimburse_queue.html", context=context)
Esempio n. 6
0
def api2_register(request):
    ip = client_ip(request)

    # 1. We're always offered an x509 client cert.
    #
    cert = request.META.get("SSL_CLIENT_CERT", None)
    if cert == None:
        logger.error("Bad request, missing cert")
        return HttpResponse(
            "No client identifier, rejecting", status=400, content_type="text/plain"
        )

    client_sha = pemToSHA256Fingerprint(cert)
    server_sha = pemToSHA256Fingerprint(request.META.get("SSL_SERVER_CERT"))

    # 2. If we do not yet now this cert - add its fingrerprint to the database
    #    and mark it as pending. Return a secret/nonce.
    #
    try:
        terminal = PettycashTerminal.objects.get(fingerprint=client_sha)

    except ObjectDoesNotExist as e:
        logger.info(
            "Fingerprint %s not found, adding to the list of unknowns" % client_sha
        )

        name = request.GET.get("name", None)
        if not name:
            logger.error(
                "Bad request, client unknown, but no name provided (%s @ %s"
                % (client_sha, ip)
            )
            return HttpResponse(
                "Bad request, missing name", status=400, content_type="text/plain"
            )

        terminal = PettycashTerminal(fingerprint=client_sha, name=name, accepted=False)
        terminal.nonce = secrets.token_hex(32)
        terminal.accepted = False
        reason = "Added on first contact; from IP address %s" % (ip)
        terminal._change_reason = reason[:100]
        terminal.save()

        logger.info("Issuing first time nonce to %s at %s" % (client_sha, ip))
        return HttpResponse(terminal.nonce, status=401, content_type="text/plain")

    # 3. If this is a new terminal; check that it has the right nonce from the initial
    #    exchange; and verify that it knows a secret (the tag id of an admin). If so
    #    auto approve it.
    #
    if not terminal.accepted:
        cutoff = timezone.now() - timedelta(minutes=settings.PAY_MAXNONCE_AGE_MINUTES)
        if cutoff > terminal.date:
            logger.info(
                "Fingerprint %s known, but too old. Issuing new one." % client_sha
            )
            terminal.nonce = secrets.token_hex(32)
            terminal.date = timezone.now()
            terminal._change_reason = (
                "Updating nonce, repeat register; but the old one was too old."
            )
            terminal.save()
            logger.info("Updating nonce for  %s at %s" % (client_sha, ip))
            return HttpResponse(terminal.nonce, status=401, content_type="text/plain")

        response = request.GET.get("response", None)
        if not response:
            logger.error(
                "Bad request, missing response for %s at %s" % (client_sha, ip)
            )
            return HttpResponse(
                "Bad request, missing response", status=400, content_type="text/plain"
            )

        # This response should be the SHA256 of nonce + tag + client-cert-sha256 + server-cert-256.
        # and the tag should be owned by someone whcih has the right admin rights.
        #
        for tag in Tag.objects.all().filter(
            owner__groups__name=settings.PETTYCASH_ADMIN_GROUP
        ):
            m = hashlib.sha256()
            m.update(terminal.nonce.encode("ascii"))
            m.update(tag.tag.encode("ascii"))
            m.update(bytes.fromhex(client_sha))
            m.update(bytes.fromhex(server_sha))
            sha = m.hexdigest()

            if sha.lower() == response.lower():
                terminal.accepted = True
                reason = "%s, IP=%s tag-=%d %s" % (
                    terminal.name,
                    ip,
                    tag.id,
                    tag.owner,
                )
                terminal._change_reason = reason[:100]
                terminal.save()
                logger.error(
                    "Terminal %s accepted, tag swipe by %s matched."
                    % (terminal, tag.owner)
                )

                emailPlain(
                    "email_accept.txt",
                    toinform=pettycash_admin_emails(),
                    context={
                        "base": settings.BASE,
                        "settings": settings,
                        "tag": tag,
                        "terminal": terminal,
                    },
                )

                # proof to the terminal that we know the tag too. This prolly
                # should be an HMAC
                #
                m = hashlib.sha256()
                m.update(tag.tag.encode("ascii"))
                m.update(bytes.fromhex(sha))
                sha = m.hexdigest()
                return HttpResponse(sha, status=200, content_type="text/plain")

        logger.error(
            "RQ ok; but response could not be correlated to a tag (%s, ip=%s, c=%s)"
            % (terminal, ip, client_sha)
        )
        return HttpResponse("Pairing failed", status=400, content_type="text/plain")

    # 4. We're talking to an approved terminal - give it its SKU list if it has
    #    been wired to a station; or just an empty list if we do not know yet.
    #
    try:
        station = PettycashStation.objects.get(terminal=terminal)
    except ObjectDoesNotExist as e:
        return JsonResponse({})

    avail = []
    for item in station.available_skus.all():
        e = {
            "name": item.name,
            "description": item.description,
            "price": item.amount.amount,
        }
        if item == station.default_sku:
            e["default"] = True
        avail.append(e)

    return JsonResponse(
        {
            "name": terminal.name,
            "description": station.description,
            "pricelist": avail,
        },
        safe=False,
    )