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)
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, }, )
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)
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, }, )
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)
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, )