Permalink
Browse files

[front] display promo code on group management page

  • Loading branch information...
aktiur committed Nov 9, 2017
1 parent c72a810 commit 2048f74a8d28234752d6b765e758fa52a01b4386
View
@@ -382,3 +382,7 @@
# allow insecure transports for OAUTHLIB in DEBUG mode
if DEBUG:
os.environ.setdefault('OAUTHLIB_INSECURE_TRANSPORT', 'y')
# Get the promo
PROMO_CODE_KEY = os.environb.get(b'PROMO_CODE_KEY', b'prout')
PROMO_CODE_TAG = os.environ.get('PROMO_CODE_TAG', 'Groupe certifié')
@@ -33,6 +33,20 @@ <h6 class="subhead">Contact</h6>
</ul>
</div>
</div>
{% if certified %}
<h3>Mon code promo pour ce mois-ci</h3>
<div style="text-align: center; margin: 2em auto;">
<span style="padding: 10px; font-weight: bolder; font-size: 2em; border: 2px solid darkgrey;">{{ group_promo_code }}</span>
</div>
<p>
Ce code peut être utilisé sur le <a href="https://materiel.lafranceinsoumise.fr">site d'achat de matériel</a>.
</p>
{% endif %}
<h3>Les animateurs et autres gestionnaires du groupe</h3>
<h4>Les animateurs du groupe</h4>
View
@@ -6,9 +6,11 @@
from django.contrib import messages
from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect, HttpResponseBadRequest
from django.core.urlresolvers import reverse_lazy, reverse
from django.conf import settings
from groups.models import SupportGroup, Membership
from groups.tasks import send_someone_joined_notification
from groups.actions.promo_codes import get_next_promo_code
from ..forms import SupportGroupForm, AddReferentForm, AddManagerForm, GroupGeocodingForm
from ..view_mixins import (
@@ -110,17 +112,18 @@ def get_forms(self):
}
def get_context_data(self, **kwargs):
referents = self.object.memberships.filter(is_referent=True).order_by('created')
managers = self.object.memberships.filter(is_manager=True, is_referent=False).order_by('created')
members = self.object.memberships.all().order_by('created')
kwargs['referents'] = self.object.memberships.filter(is_referent=True).order_by('created')
kwargs['managers'] = self.object.memberships.filter(is_manager=True, is_referent=False).order_by('created')
kwargs['members'] = self.object.memberships.all().order_by('created')
kwargs['certified'] = self.object.tags.filter(label=settings.PROMO_CODE_TAG).exists()
if kwargs['certified']:
kwargs['group_promo_code'] = get_next_promo_code(self.object)
return super().get_context_data(
referents=referents,
managers=managers,
members=members,
is_referent=self.user_membership is not None and self.user_membership.is_referent,
is_manager=self.user_membership is not None and (self.user_membership.is_referent or self.user_membership.is_manager),
**self.get_forms()
**self.get_forms(),
**kwargs
)
def get(self, request, *args, **kwargs):
No changes.
@@ -0,0 +1,95 @@
import struct
import base64
import hmac
import hashlib
from datetime import date
from django.conf import settings
from django.utils import timezone
REFERENCE_DATE = date(2017, 1, 1)
GROUP_ID_SIZE = 6
SIGNATURE_SIZE = 6
DIGESTMOD = hashlib.sha1
BASE64ENC = base64.urlsafe_b64encode
def generate_date_fragment(expiration_date):
"""Generate a two-character encoding of the expiration date
That encoding is done that way:
* Compute the number of days since the REFERENCE_DATE
* encode it as two bytes (low-endian)
* left shift the second byte by four bits
It should now look that way (second hexdigit of second byte is all zero after left shift) :
xxxx xxxx xxxx 0000
Only twelve bits of data ==> can be encoded by two base64 characters
* Encode it in base64 and keep the two first characters
:param expiration_date: a date corresponding to the day the promo code should expire
:return: base64 encoding of the input date (bytes object)
"""
days = (expiration_date - REFERENCE_DATE).days
assert days < 4096 # 2^12 or the maximum value that can be set in 2 Base64 characters
# use little-endian for packing
b = bytearray(struct.pack('<I', days)[:2])
b[1] <<= 4
return BASE64ENC(b)[:2]
def generate_msg_part_for_group(group, expiration_date):
"""Generate the msg part of the promo code for a specific group
The msg part is made of :
* the expiration date (as 2 base64 characters)
* 6 characters corresponding to the support group id
:param group: the group for which the promo code must be generated
:param expiration_date: the expiration date for the promo code
:return:
"""
date_fragment = generate_date_fragment(expiration_date)
# let's hash the group part to make sure it works whatever the uuid generation mode
# keep only the strictly minimum number of bytes
keep_bytes = (GROUP_ID_SIZE * 3 // 4) + 1
group_bytes = DIGESTMOD(group.pk.bytes).digest()[:keep_bytes]
# let's use the first GROUP_ID_SIZE characters of the base64 encoding
group_fragment = BASE64ENC(group_bytes)[:GROUP_ID_SIZE]
return date_fragment + group_fragment
def sign_code(msg):
keep_bytes = (SIGNATURE_SIZE * 3 // 4) + 1
sig_bytes = hmac.new(
key=settings.PROMO_CODE_KEY,
msg=msg,
digestmod=DIGESTMOD
).digest()[:keep_bytes]
signature_frag = BASE64ENC(sig_bytes)[:SIGNATURE_SIZE]
return msg + signature_frag
def generate_code_for_group(group, expiration_date):
msg = generate_msg_part_for_group(group, expiration_date)
return sign_code(msg)
def get_next_promo_code(group):
today = timezone.now().astimezone(timezone.get_default_timezone())
if today.month == 12:
expiration_date = date(today.year+1, 1, 1)
else:
expiration_date = date(today.year, today.month+1, 1)
return generate_code_for_group(group, expiration_date)

0 comments on commit 2048f74

Please sign in to comment.