Browse Source

update deps & code format lambdainst

master
alice 2 months ago
parent
commit
a7258ca37d
15 changed files with 665 additions and 484 deletions
  1. +49
    -20
      lambdainst/admin.py
  2. +30
    -19
      lambdainst/forms.py
  3. +47
    -25
      lambdainst/graphs.py
  4. +30
    -24
      lambdainst/middleware.py
  5. +21
    -10
      lambdainst/models.py
  6. +57
    -55
      lambdainst/openvpn.py
  7. +3
    -3
      lambdainst/tasks.py
  8. +4
    -5
      lambdainst/templatetags/active.py
  9. +2
    -2
      lambdainst/templatetags/bw.py
  10. +8
    -4
      lambdainst/tests/online.py
  11. +123
    -95
      lambdainst/tests/units.py
  12. +11
    -12
      lambdainst/urls.py
  13. +235
    -167
      lambdainst/views.py
  14. +43
    -40
      poetry.lock
  15. +2
    -3
      pyproject.toml

+ 49
- 20
lambdainst/admin.py View File

@@ -15,18 +15,29 @@ from payments.admin import UserCouponInline, UserLedgerInline


def make_user_link(user):
change_url = resolve_url('admin:auth_user_change', user.id)
change_url = resolve_url("admin:auth_user_change", user.id)
return format_html('<a href="{}">{}</a>', change_url, user.username)


class VPNUserInline(admin.StackedInline):
model = VPNUser
can_delete = False
fk_name = 'user'

fields = ('notes', 'expiration', 'last_expiry_notice', 'notify_expiration',
'last_vpn_auth_fail_notice', 'notify_vpn_auth_fail',
'trial_periods_given', 'referrer_a', 'campaign', 'last_vpn_auth', 'last_core_sync')
readonly_fields = ('referrer_a', 'last_vpn_auth', 'last_core_sync', 'campaign')
fk_name = "user"

fields = (
"notes",
"expiration",
"last_expiry_notice",
"notify_expiration",
"last_vpn_auth_fail_notice",
"notify_vpn_auth_fail",
"trial_periods_given",
"referrer_a",
"campaign",
"last_vpn_auth",
"last_core_sync",
)
readonly_fields = ("referrer_a", "last_vpn_auth", "last_core_sync", "campaign")

def referrer_a(self, object):
if not object.referrer:
@@ -38,40 +49,58 @@ class VPNUserInline(admin.StackedInline):
else:
s += _("(not rewarded)")
return s

referrer_a.allow_tags = True
referrer_a.short_description = _("Referrer")

def is_paid(self, object):
return object.is_paid

is_paid.boolean = True
is_paid.short_description = _("Is paid?")


class UserAdmin(UserAdmin):
inlines = (VPNUserInline, UserLedgerInline, UserCouponInline)
list_display = ('username', 'email', 'is_staff', 'date_joined', 'is_paid')
ordering = ('-date_joined', )
list_display = ("username", "email", "is_staff", "date_joined", "is_paid")
ordering = ("-date_joined",)
fieldsets = (
(None, {'fields': ('username', 'password', 'email', 'links')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups', 'user_permissions')}),
(None, {"fields": ("username", "password", "email", "links")}),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
(
_("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
)
},
),
)
readonly_fields = ('last_login', 'date_joined', 'links')
actions = (django_lcore.core_sync_action, )
readonly_fields = ("last_login", "date_joined", "links")
actions = (django_lcore.core_sync_action,)

def is_paid(self, object):
return object.vpnuser.is_paid

is_paid.boolean = True
is_paid.short_description = _("Is paid?")

def links(self, object):
payments_url = resolve_url('admin:payments_payment_changelist')
tickets_url = resolve_url('admin:tickets_ticket_changelist')
payments_url = resolve_url("admin:payments_payment_changelist")
tickets_url = resolve_url("admin:tickets_ticket_changelist")
fmt = '<a href="{}?user__id__exact={}">{}</a>'
return format_html(fmt + " - " + fmt,
payments_url, object.id, "Payments",
tickets_url, object.id, "Tickets",
return format_html(
fmt + " - " + fmt,
payments_url,
object.id,
"Payments",
tickets_url,
object.id,
"Tickets",
)

def save_model(self, request, obj, form, change):


+ 30
- 19
lambdainst/forms.py View File

@@ -7,14 +7,14 @@ from django.utils.safestring import mark_safe

class FormPureRender:
def as_pure_aligned(self):
html = ''
html = ""
for f in self:
html += '<div class="pure-control-group">\n'
html += str(f.label_tag()) + '\n'
html += str(f) + '\n'
html += str(f.label_tag()) + "\n"
html += str(f) + "\n"
if f.errors:
html += str(f.errors) + '\n'
html += '</div>\n'
html += str(f.errors) + "\n"
html += "</div>\n"
return mark_safe(html)


@@ -30,36 +30,43 @@ class UserField(forms.RegexField):

class SignupForm(forms.Form, FormPureRender):
username = UserField(
label=_("Username"), min_length=2, max_length=16, regex='^[a-zA-Z0-9_-]+$',
widget=forms.TextInput(attrs={'required': 'true',
'pattern': '[a-zA-Z0-9_-]{2,32}',
'placeholder': _("Username"),
'autofocus': 'true'})
label=_("Username"),
min_length=2,
max_length=16,
regex="^[a-zA-Z0-9_-]+$",
widget=forms.TextInput(
attrs={
"required": "true",
"pattern": "[a-zA-Z0-9_-]{2,32}",
"placeholder": _("Username"),
"autofocus": "true",
}
),
)
password = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput(attrs={'placeholder': _("Anything")})
widget=forms.PasswordInput(attrs={"placeholder": _("Anything")}),
)
password2 = forms.CharField(
label=_("Repeat"),
widget=forms.PasswordInput(attrs={'placeholder': _("Same Anything")})
widget=forms.PasswordInput(attrs={"placeholder": _("Same Anything")}),
)
email = forms.EmailField(
label=_("E-Mail"),
widget=forms.EmailInput(attrs={'placeholder': _("E-Mail")}),
widget=forms.EmailInput(attrs={"placeholder": _("E-Mail")}),
required=False,
)

def clean_password(self):
if self.data['password'] != self.data['password2']:
if self.data["password"] != self.data["password2"]:
raise forms.ValidationError(_("Passwords are not the same"))
return self.data['password']
return self.data["password"]


class ReqEmailForm(forms.Form, FormPureRender):
email = forms.EmailField(
label=_("E-Mail"),
widget=forms.EmailInput(attrs={'placeholder': _("E-Mail")}),
widget=forms.EmailInput(attrs={"placeholder": _("E-Mail")}),
)


@@ -73,7 +80,11 @@ def wg_pk_validator(s):


class WgPeerForm(forms.Form):
public_key = forms.CharField(min_length=3, max_length=100, strip=True, required=False, validators=[
wg_pk_validator
])
public_key = forms.CharField(
min_length=3,
max_length=100,
strip=True,
required=False,
validators=[wg_pk_validator],
)
name = forms.CharField(max_length=21, required=False)

+ 47
- 25
lambdainst/graphs.py View File

@@ -8,8 +8,8 @@ from payments.models import Payment


PERIOD_VERBOSE_NAME = {
'y': "per month",
'm': "per day",
"y": "per month",
"m": "per day",
}


@@ -18,9 +18,23 @@ def monthdelta(date, delta):
y = date.year + (date.month + delta - 1) // 12
if not m:
m = 12
d = min(date.day, [31, 29 if y % 4 == 0 and not y % 400 == 0
else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
][m - 1])
d = min(
date.day,
[
31,
29 if y % 4 == 0 and not y % 400 == 0 else 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
][m - 1],
)
return date.replace(day=d, month=m, year=y)


@@ -38,57 +52,64 @@ def last_months(n=12):

def time_filter_future(period, m, df):
def _filter(o):
if period == 'm':
if period == "m":
return df(o).date() <= m
if period == 'y':
if period == "y":
return df(o).date().replace(day=1) <= m

return _filter


def time_filter_between(period, m, df):
def _filter(o):
if period == 'm':
return df(o).year == m.year and df(o).month == m.month and df(o).day == m.day
if period == "m":
return (
df(o).year == m.year and df(o).month == m.month and df(o).day == m.day
)
return df(o).date() <= m and df(o).date() > (m - timedelta(days=1))
if period == 'y':
if period == "y":
return df(o).year == m.year and df(o).month == m.month
return (df(o).date().replace(day=1) <= m and
df(o).date().replace(day=1) > (m - timedelta(days=30)))
return df(o).date().replace(day=1) <= m and df(o).date().replace(day=1) > (
m - timedelta(days=30)
)

return _filter


def users_graph(period):
chart = pygal.Line(fill=True, x_label_rotation=75, show_legend=False)
chart.title = 'Users %s' % PERIOD_VERBOSE_NAME[period]
chart.title = "Users %s" % PERIOD_VERBOSE_NAME[period]
chart.x_labels = []
values = []
gen = last_days(30) if period == 'm' else last_months(12)
gen = last_days(30) if period == "m" else last_months(12)
users = User.objects.all()

for m in gen:
filter_ = time_filter_future(period, m, lambda o: o.date_joined)
users_filtered = filter(filter_, users)
values.append(len(list(users_filtered)))
chart.x_labels.append('%02d/%02d' % (m.month, m.day))
chart.x_labels.append("%02d/%02d" % (m.month, m.day))

chart.add('Users', values)
chart.add("Users", values)
return chart.render()


def payments_paid_graph(period):
chart = pygal.StackedBar(x_label_rotation=75, show_legend=True)
chart.x_labels = []
gen = list(last_days(30) if period == 'm' else last_months(12))
gen = list(last_days(30) if period == "m" else last_months(12))

chart.title = 'Payments %s in €' % (PERIOD_VERBOSE_NAME[period])
chart.title = "Payments %s in €" % (PERIOD_VERBOSE_NAME[period])

for m in gen:
chart.x_labels.append('%02d/%02d' % (m.month, m.day))
chart.x_labels.append("%02d/%02d" % (m.month, m.day))

values = dict()
for backend_id, backend in BACKENDS.items():
values = []
payments = list(Payment.objects.filter(status='confirmed', backend_id=backend_id))
payments = list(
Payment.objects.filter(status="confirmed", backend_id=backend_id)
)

for m in gen:
filter_ = time_filter_between(period, m, lambda o: o.created)
@@ -103,17 +124,19 @@ def payments_paid_graph(period):
def payments_success_graph(period):
chart = pygal.StackedBar(x_label_rotation=75, show_legend=True)
chart.x_labels = []
gen = list(last_days(30) if period == 'm' else last_months(12))
gen = list(last_days(30) if period == "m" else last_months(12))

chart.title = 'Successful payments %s' % (PERIOD_VERBOSE_NAME[period])
chart.title = "Successful payments %s" % (PERIOD_VERBOSE_NAME[period])

for m in gen:
chart.x_labels.append('%02d/%02d' % (m.month, m.day))
chart.x_labels.append("%02d/%02d" % (m.month, m.day))

values = dict()
for backend_id, backend in BACKENDS.items():
values = []
payments = list(Payment.objects.filter(status='confirmed', backend_id=backend_id))
payments = list(
Payment.objects.filter(status="confirmed", backend_id=backend_id)
)

for m in gen:
filter_ = time_filter_between(period, m, lambda o: o.created)
@@ -123,4 +146,3 @@ def payments_success_graph(period):
chart.add(backend_id, values)

return chart.render()


+ 30
- 24
lambdainst/middleware.py View File

@@ -8,10 +8,10 @@ from .models import User

class ReferrerMiddleware(MiddlewareMixin):
def process_request(self, request):
if 'ref' in request.GET:
id = request.GET['ref']
elif 'referrer' in request.COOKIES:
id = request.COOKIES['referrer']
if "ref" in request.GET:
id = request.GET["ref"]
elif "referrer" in request.COOKIES:
id = request.COOKIES["referrer"]
else:
return

@@ -25,33 +25,36 @@ class ReferrerMiddleware(MiddlewareMixin):
except User.DoesNotExist:
return

request.session['referrer'] = u.id
request.session["referrer"] = u.id

def process_response(self, request, response):
id = request.session.get('referrer')
id = request.session.get("referrer")
if not id:
return response

max_age = 365 * 24 * 60 * 60
expires = (datetime.utcnow() + timedelta(seconds=max_age))
expires = datetime.utcnow() + timedelta(seconds=max_age)
expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
response.set_cookie('referrer', id,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
secure=settings.SESSION_COOKIE_SECURE or None)
response.set_cookie(
"referrer",
id,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
secure=settings.SESSION_COOKIE_SECURE or None,
)
return response


class CampaignMiddleware(MiddlewareMixin):
GET_FIELDS = ['pk_campaign', 'utm_campaign', 'utm_medium', 'utm_source']
GET_FIELDS = ["pk_campaign", "utm_campaign", "utm_medium", "utm_source"]

def _get_name(self, request):
for f in self.GET_FIELDS:
if f in request.GET and request.GET[f]:
return request.GET[f]
if 'campaign' in request.COOKIES:
return request.COOKIES['campaign']
if "campaign" in request.COOKIES:
return request.COOKIES["campaign"]
return None

def process_request(self, request):
@@ -61,22 +64,25 @@ class CampaignMiddleware(MiddlewareMixin):

name = name.strip()

if len(name) >= 64 or not re.match('^[a-zA-Z0-9_.:-]+$', name):
if len(name) >= 64 or not re.match("^[a-zA-Z0-9_.:-]+$", name):
return

request.session['campaign'] = name
request.session["campaign"] = name

def process_response(self, request, response):
name = request.session.get('campaign')
name = request.session.get("campaign")
if not name:
return response

max_age = 365 * 24 * 60 * 60
expires = (datetime.utcnow() + timedelta(seconds=max_age))
expires = datetime.utcnow() + timedelta(seconds=max_age)
expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
response.set_cookie('campaign', name,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
secure=settings.SESSION_COOKIE_SECURE or None)
response.set_cookie(
"campaign",
name,
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,
secure=settings.SESSION_COOKIE_SECURE or None,
)
return response

+ 21
- 10
lambdainst/models.py View File

@@ -2,7 +2,11 @@ import random
from django.db import models, IntegrityError
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django_lcore.core import LcoreUserProfileMethods, setup_sync_hooks, VPN_AUTH_STORAGE
from django_lcore.core import (
LcoreUserProfileMethods,
setup_sync_hooks,
VPN_AUTH_STORAGE,
)

from payments.models import BaseSubUser

@@ -11,7 +15,7 @@ prng = random.SystemRandom()

def random_gift_code():
charset = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"
return ''.join([prng.choice(charset) for n in range(10)])
return "".join([prng.choice(charset) for n in range(10)])


class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods):
@@ -33,8 +37,9 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods):
last_vpn_auth = models.DateTimeField(blank=True, null=True)
last_core_sync = models.DateTimeField(blank=True, null=True)

referrer = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL,
related_name='referrals')
referrer = models.ForeignKey(
User, blank=True, null=True, on_delete=models.SET_NULL, related_name="referrals"
)
referrer_used = models.BooleanField(default=False)
campaign = models.CharField(blank=True, null=True, max_length=64)

@@ -49,15 +54,20 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods):

def on_payment_confirmed(self, payment):
if self.referrer and not self.referrer_used:
self.referrer.vpnuser.add_paid_months(1, 'referrer', f"rewarded for {self.user.username} (payment #{payment.id})")
self.referrer.vpnuser.add_paid_months(
1,
"referrer",
f"rewarded for {self.user.username} (payment #{payment.id})",
)
self.referrer.vpnuser.save()
self.referrer_used = True
self.save()

def lcore_sync(self):
if VPN_AUTH_STORAGE == 'inst':
if VPN_AUTH_STORAGE == "inst":
return
from lambdainst.tasks import push_user

push_user.delay(user_id=self.user.id)

def notify_payment(self, payment):
@@ -72,12 +82,13 @@ class VPNUser(models.Model, BaseSubUser, LcoreUserProfileMethods):
def __str__(self):
return self.user.username


setup_sync_hooks(User, VPNUser)

#from django.db.models.signals import post_save
#from django.dispatch import receiver
#@receiver(post_save, sender=User)
#def create_vpnuser(sender, instance, created, **kwargs):
# from django.db.models.signals import post_save
# from django.dispatch import receiver
# @receiver(post_save, sender=User)
# def create_vpnuser(sender, instance, created, **kwargs):
# if created:
# try:
# VPNUser.objects.create(user=instance)


+ 57
- 55
lambdainst/openvpn.py View File

@@ -7,83 +7,86 @@ from django.conf import settings
CA_CERT = settings.OPENVPN_CA

CONFIG_OS = (
('windows', _("Windows")),
('android', _("Android")),
('ubuntu', _("Ubuntu")),
('osx', _("OS X")),
('ios', _("iOS")),
('chromeos', _("Chrome OS")),
('freebox', _("Freebox")),
('other', _("Other / GNU/Linux")),
("windows", _("Windows")),
("android", _("Android")),
("ubuntu", _("Ubuntu")),
("osx", _("OS X")),
("ios", _("iOS")),
("chromeos", _("Chrome OS")),
("freebox", _("Freebox")),
("other", _("Other / GNU/Linux")),
)

PROTOCOLS = (
('udp', _("UDP (default)")),
('tcp', _("TCP")),
('udpl', _("UDP (low MTU)")),
("udp", _("UDP (default)")),
("tcp", _("TCP")),
("udpl", _("UDP (low MTU)")),
)


def _make_onc(username, name, hostname, port, protocol, http_proxy=None, ipv6=True):
cert_id = '{%s}' % uuid.uuid4()
vpn_id = '{%s}' % uuid.uuid4()
cert_id = "{%s}" % uuid.uuid4()
vpn_id = "{%s}" % uuid.uuid4()

openvpn_config = {
'ServerCARef': cert_id,
'ClientCertType': 'None',
'CompLZO': 'true',
'Port': port,
'Proto': protocol,
'ServerPollTimeout': 10,
'NsCertType': 'server',
'Username': username,
"ServerCARef": cert_id,
"ClientCertType": "None",
"CompLZO": "true",
"Port": port,
"Proto": protocol,
"ServerPollTimeout": 10,
"NsCertType": "server",
"Username": username,
}
cert = {
'GUID': cert_id,
'Type': 'Authority',
'X509': CA_CERT.strip(),
"GUID": cert_id,
"Type": "Authority",
"X509": CA_CERT.strip(),
}
vpn = {
'GUID': vpn_id,
'Name': name,
'Type': 'VPN',
'VPN': {
'Type': 'OpenVPN',
'Host': hostname,
'OpenVPN': openvpn_config,
"GUID": vpn_id,
"Name": name,
"Type": "VPN",
"VPN": {
"Type": "OpenVPN",
"Host": hostname,
"OpenVPN": openvpn_config,
},
}

return json.dumps({
'type': 'UnencryptedConfiguration',
'Certificates': [cert],
'NetworkConfigurations': [vpn],
}, indent=2)
return json.dumps(
{
"type": "UnencryptedConfiguration",
"Certificates": [cert],
"NetworkConfigurations": [vpn],
},
indent=2,
)


def make_config(username, gw_name, os, protocol, http_proxy=None, ipv6=True):

use_frag = protocol == 'udpl' and os != 'ios'
ipv6 = ipv6 and (os != 'freebox')
http_proxy = http_proxy if protocol == 'tcp' else None
resolvconf = os in ('ubuntu', 'other')
use_frag = protocol == "udpl" and os != "ios"
ipv6 = ipv6 and (os != "freebox")
http_proxy = http_proxy if protocol == "tcp" else None
resolvconf = os in ("ubuntu", "other")

openvpn_proto = {'udp': 'udp', 'udpl': 'udp', 'tcp': 'tcp'}
openvpn_ports = {'udp': 1196, 'udpl': 1194, 'tcp': 443}
openvpn_proto = {"udp": "udp", "udpl": "udp", "tcp": "tcp"}
openvpn_ports = {"udp": 1196, "udpl": 1194, "tcp": 443}

hostname = 'gw.%s.204vpn.net' % gw_name
hostname = "gw.%s.204vpn.net" % gw_name
port = openvpn_ports[protocol]
proto = openvpn_proto[protocol]

if os == 'chromeos':
if os == "chromeos":
name = "CCrypto VPN"
if gw_name != 'random':
if gw_name != "random":
name += " " + gw_name.upper()
return _make_onc(username, name, hostname, port, proto, http_proxy, ipv6)

remote = str(hostname)
remote += ' ' + str(port)
remote += ' ' + proto
remote += " " + str(port)
remote += " " + proto

config = """\
# +----------------------------+
@@ -106,9 +109,11 @@ remote {remote}

auth-user-pass

""".format(remote=remote)
""".format(
remote=remote
)

if os == 'ios':
if os == "ios":
# i'd like to note here how much i hate OpenVPN
config += "redirect-gateway ipv6\n"
config += 'push "route 0.0.0.0 128.0.0.0"\n'
@@ -133,16 +138,13 @@ auth-user-pass
config += "down /etc/openvpn/update-resolv-conf\n"
config += "\n"

if os == 'windows':
if os == "windows":
config += "register-dns\n"
config += "\n"

config += "<ca>\n%s\n</ca>" % CA_CERT

if os == 'windows':
config = config.replace('\n', '\r\n')
if os == "windows":
config = config.replace("\n", "\r\n")

return config




+ 3
- 3
lambdainst/tasks.py View File

@@ -69,7 +69,7 @@ def notify_vpn_auth_fails():
NOTIFY_EVERY = timedelta(days=180) # users notified recently should be ignored

def find_fails_for_user(u):
""" returns true if there are 'many' recent fails """
"""returns true if there are 'many' recent fails"""
path = api.info["current_instance"] + "/users/" + u.username + "/auth-events/"
try:
r = api.get(path)
@@ -181,7 +181,7 @@ def notify_vpn_auth_fails():

@shared_task
def notify_account_expiration():
""" Notify users near the end of their subscription """
"""Notify users near the end of their subscription"""
from_email = settings.DEFAULT_FROM_EMAIL

for v in parse_integer_list(site_config.NOTIFY_DAYS_BEFORE):
@@ -213,7 +213,7 @@ def notify_account_expiration():


def get_next_expirations(days=3):
""" Gets users whose subscription will expire in some days """
"""Gets users whose subscription will expire in some days"""

limit_date = timezone.now() + timedelta(days=days)



+ 4
- 5
lambdainst/templatetags/active.py View File

@@ -9,11 +9,10 @@ register = template.Library()
@register.simple_tag(takes_context=True)
def active(context, pattern_or_urlname):
try:
pattern = '^' + reverse(pattern_or_urlname)
pattern = "^" + reverse(pattern_or_urlname)
except NoReverseMatch:
pattern = pattern_or_urlname
path = context['request'].path
path = context["request"].path
if re.search(pattern, path):
return 'active'
return ''

return "active"
return ""

+ 2
- 2
lambdainst/templatetags/bw.py View File

@@ -11,7 +11,7 @@ def bwformat(bps):
try:
bps = float(bps)
except (TypeError, ValueError, UnicodeDecodeError):
value = ungettext("%(bw)d bps", "%(bw)d bps", 0) % {'bw': 0}
value = ungettext("%(bw)d bps", "%(bw)d bps", 0) % {"bw": 0}
return avoid_wrapping(value)

filesize_number_format = lambda value: formats.number_format(round(value, 1), -1)
@@ -23,7 +23,7 @@ def bwformat(bps):
P = 1 * 10 ** 15

if bps < K:
value = ungettext("%(size)d bps", "%(size)d bps", bps) % {'size': bps}
value = ungettext("%(size)d bps", "%(size)d bps", bps) % {"size": bps}
elif bps < M:
value = ugettext("%s Kbps") % filesize_number_format(bps / K)
elif bps < G:


+ 8
- 4
lambdainst/tests/online.py View File

@@ -103,12 +103,14 @@ class OnlineStripeTests(BaseOnlineTest):
self.selenium.find_element_by_xpath('//button[@type="submit"]').click()

self.wait.until(
EC.presence_of_element_located((By.XPATH, '//*[contains(text(), "Waiting for payment")]'))
EC.presence_of_element_located(
(By.XPATH, '//*[contains(text(), "Waiting for payment")]')
)
)

def check_active():
# refresh payment as we dont have a worker
p = Payment.objects.order_by('id').first()
p = Payment.objects.order_by("id").first()
p.refresh()

self.selenium.refresh()
@@ -159,12 +161,14 @@ class OnlineStripeTests(BaseOnlineTest):
self.selenium.find_element_by_xpath('//button[@type="submit"]').click()

self.wait.until(
EC.presence_of_element_located((By.XPATH, '//*[contains(text(), "Your subscription is processing.")]'))
EC.presence_of_element_located(
(By.XPATH, '//*[contains(text(), "Your subscription is processing.")]')
)
)

def check_active():
# refresh sub as we dont have a worker
p = Subscription.objects.order_by('id').first()
p = Subscription.objects.order_by("id").first()
p.refresh()

self.selenium.refresh()


+ 123
- 95
lambdainst/tests/units.py View File

@@ -14,24 +14,24 @@ from payments.models import Payment, Subscription

class UserTestMixin:
def assertRemaining(self, vpnuser, time, delta=5):
""" Check that the vpnuser will expire in time (+/- 5 seconds) """
"""Check that the vpnuser will expire in time (+/- 5 seconds)"""
exp = vpnuser.expiration or timezone.now()
seconds = (exp - timezone.now() - time).total_seconds()
self.assertAlmostEqual(seconds, 0, delta=delta)



class UserModelReferrerTest(TestCase, UserTestMixin):
def setUp(self):
self.referrer = User.objects.create_user('ref')
self.referrer = User.objects.create_user("ref")

self.without_ref = User.objects.create_user('aaaa')
self.without_ref = User.objects.create_user("aaaa")

self.with_ref = User.objects.create_user('bbbb')
self.with_ref = User.objects.create_user("bbbb")
self.with_ref.vpnuser.referrer = self.referrer

self.payment = Payment.objects.create(
user=self.with_ref, status='confirmed', amount=300, time=timedelta(days=30))
user=self.with_ref, status="confirmed", amount=300, time=timedelta(days=30)
)

def test_no_ref(self):
self.without_ref.vpnuser.on_payment_confirmed(self.payment)
@@ -40,125 +40,153 @@ class UserModelReferrerTest(TestCase, UserTestMixin):
self.with_ref.vpnuser.on_payment_confirmed(self.payment)
self.assertTrue(self.with_ref.vpnuser.referrer_used)
self.assertEqual(self.with_ref.vpnuser.referrer, self.referrer)
self.assertRemaining(self.referrer.vpnuser, timedelta(days=30), delta=24*3600*3)
self.assertRemaining(
self.referrer.vpnuser, timedelta(days=30), delta=24 * 3600 * 3
)


class SignupViewTest(TestCase):
def test_form(self):
response = self.client.get('/account/signup')
response = self.client.get("/account/signup")
self.assertEqual(response.status_code, 200)
self.assertIsInstance(response.context['form'], SignupForm)
self.assertIsInstance(response.context["form"], SignupForm)

def test_post(self):
response = self.client.post('/account/signup', {
'username': 'test_un', 'password': 'test_pw', 'password2': 'test_pw'})
self.assertRedirects(response, '/account/')
response = self.client.post(
"/account/signup",
{"username": "test_un", "password": "test_pw", "password2": "test_pw"},
)
self.assertRedirects(response, "/account/")

user = User.objects.get(username='test_un')
self.assertTrue(user.check_password('test_pw'))
user = User.objects.get(username="test_un")
self.assertTrue(user.check_password("test_pw"))

def test_post_error(self):
response = self.client.post('/account/signup', {
'username': 'test_un', 'password': 'test_pw', 'password2': 'qsdf'})
response = self.client.post(
"/account/signup",
{"username": "test_un", "password": "test_pw", "password2": "qsdf"},
)
self.assertEqual(response.status_code, 200)
self.assertIsInstance(response.context['form'], SignupForm)
self.assertFormError(response, 'form', 'password',
'Passwords are not the same')
self.assertIsInstance(response.context["form"], SignupForm)
self.assertFormError(response, "form", "password", "Passwords are not the same")

def test_post_referrer(self):
ref = User.objects.create_user('referrer')
ref = User.objects.create_user("referrer")

response = self.client.post('/account/signup?ref=%d' % ref.id, {
'username': 'test_un', 'password': 'test_pw', 'password2': 'test_pw'})
self.assertRedirects(response, '/account/')
response = self.client.post(
"/account/signup?ref=%d" % ref.id,
{"username": "test_un", "password": "test_pw", "password2": "test_pw"},
)
self.assertRedirects(response, "/account/")

user = User.objects.get(username='test_un')
self.assertTrue(user.check_password('test_pw'))
user = User.objects.get(username="test_un")
self.assertTrue(user.check_password("test_pw"))
self.assertEqual(user.vpnuser.referrer, ref)


class AccountViewsTest(TestCase, UserTestMixin):
def setUp(self):
User.objects.create_user('test', None, 'test_pw')
self.client.login(username='test', password='test_pw')
User.objects.create_user("test", None, "test_pw")
self.client.login(username="test", password="test_pw")

def test_account(self):
response = self.client.get('/account/')
response = self.client.get("/account/")
self.assertEqual(response.status_code, 200)

def test_settings_form(self):
response = self.client.get('/account/settings')
response = self.client.get("/account/settings")
self.assertEqual(response.status_code, 200)

def print_message(self, response):
from django.contrib.messages import get_messages

messages = list(get_messages(response.wsgi_request))
for m in messages:
print(f"[message: {m.message!r} level={m.level} tags={m.tags!r}]")

def test_settings_post_email(self):
response = self.client.post('/account/settings', {
'action': 'email',
'current_password': 'test_pw',
'email': 'new_email@example.com'})
response = self.client.post(
"/account/settings",
{
"action": "email",
"current_password": "test_pw",
"email": "new_email@example.com",
},
)
self.assertEqual(response.status_code, 302)

user = User.objects.get(username='test')
self.assertEqual(user.email, 'new_email@example.com')
user = User.objects.get(username="test")
self.assertEqual(user.email, "new_email@example.com")

def test_settings_post_email_fail(self):
response = self.client.post('/account/settings', {
'action': 'email',
'current_password': 'not_test_pw',
'email': 'new_email@example.com'})
response = self.client.post(
"/account/settings",
{
"action": "email",
"current_password": "not_test_pw",
"email": "new_email@example.com",
},
)
self.assertEqual(response.status_code, 302)

user = User.objects.get(username='test')
self.assertNotEqual(user.email, 'new_email@example.com')
user = User.objects.get(username="test")
self.assertNotEqual(user.email, "new_email@example.com")

def test_settings_post_pw(self):
response = self.client.post('/account/settings', {
'action': 'password',
'current_password': 'test_pw',
'password': 'new_test_pw', 'password2': 'new_test_pw'})
response = self.client.post(
"/account/settings",
{
"action": "password",
"current_password": "test_pw",
"password": "new_test_pw",
"password2": "new_test_pw",
},
)
self.assertEqual(response.status_code, 302)

user = User.objects.get(username='test')
self.assertTrue(user.check_password('new_test_pw'))
user = User.objects.get(username="test")
self.assertTrue(user.check_password("new_test_pw"))

def test_settings_post_pw_fail(self):
response = self.client.post('/account/settings', {
'action': 'password',
'current_password': 'oops',
'password': 'new_test_pw',
'password2': 'new_test_pw'})
response = self.client.post(
"/account/settings",
{
"action": "password",
"current_password": "oops",
"password": "new_test_pw",
"password2": "new_test_pw",
},
)
self.assertEqual(response.status_code, 302)

response = self.client.post('/account/settings', {
'action': 'password',
'current_password': 'test_pw',
'password': 'new_test_pw2',
'password2': 'new_test_pw_qsdfg'})
response = self.client.post(
"/account/settings",
{
"action": "password",
"current_password": "test_pw",
"password": "new_test_pw2",
"password2": "new_test_pw_qsdfg",
},
)
self.assertEqual(response.status_code, 302)

user = User.objects.get(username='test')
self.assertFalse(user.check_password('new_test_pw'))
self.assertFalse(user.check_password('new_test_pw2'))
self.assertTrue(user.check_password('test_pw'))
user = User.objects.get(username="test")
self.assertFalse(user.check_password("new_test_pw"))
self.assertFalse(user.check_password("new_test_pw2"))
self.assertTrue(user.check_password("test_pw"))


class CACrtViewTest(TestCase):
def test_ca_crt(self):
with self.settings(OPENVPN_CA='test ca'):
response = self.client.get('/ca.crt')
with self.settings(OPENVPN_CA="test ca"):
response = self.client.get("/ca.crt")
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/x-x509-ca-cert')
self.assertEqual(response.content, b'test ca')
self.assertEqual(response["Content-Type"], "application/x-x509-ca-cert")
self.assertEqual(response.content, b"test ca")


def email_text(body):
return body.replace('\n', ' ') \
.replace('\xa0', ' ') # nbsp
return body.replace("\n", " ").replace("\xa0", " ") # nbsp


class ExpireNotifyTest(TestCase):
@@ -167,68 +195,68 @@ class ExpireNotifyTest(TestCase):

def test_notify_first(self):
out = StringIO()
u = User.objects.create_user('test_username', 'test@example.com', 'testpw')
u.vpnuser.add_paid_time(timedelta(days=2), 'initial')
u = User.objects.create_user("test_username", "test@example.com", "testpw")
u.vpnuser.add_paid_time(timedelta(days=2), "initial")
u.vpnuser.save()

call_command('expire_notify', stdout=out)
call_command("expire_notify", stdout=out)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ['test@example.com'])
self.assertIn('expire in 1 day', email_text(mail.outbox[0].body))
self.assertEqual(mail.outbox[0].to, ["test@example.com"])
self.assertIn("expire in 1 day", email_text(mail.outbox[0].body))

u = User.objects.get(username='test_username')
self.assertAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(),
delta=timedelta(minutes=1))
u = User.objects.get(username="test_username")
self.assertAlmostEqual(
u.vpnuser.last_expiry_notice, timezone.now(), delta=timedelta(minutes=1)
)

def test_notify_second(self):
out = StringIO()
u = User.objects.create_user('test_username', 'test@example.com', 'testpw')
u = User.objects.create_user("test_username", "test@example.com", "testpw")
u.vpnuser.last_expiry_notice = timezone.now() - timedelta(days=2)
u.vpnuser.add_paid_time(timedelta(days=1), 'initial')
u.vpnuser.add_paid_time(timedelta(days=1), "initial")
u.vpnuser.save()

call_command('expire_notify', stdout=out)
call_command("expire_notify", stdout=out)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ['test@example.com'])
self.assertIn('expire in 23 hours, 59 minutes', email_text(mail.outbox[0].body))
self.assertEqual(mail.outbox[0].to, ["test@example.com"])
self.assertIn("expire in 23 hours, 59 minutes", email_text(mail.outbox[0].body))

u = User.objects.get(username='test_username')
self.assertAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(),
delta=timedelta(minutes=1))
u = User.objects.get(username="test_username")
self.assertAlmostEqual(
u.vpnuser.last_expiry_notice, timezone.now(), delta=timedelta(minutes=1)
)

def test_notify_subscription(self):
out = StringIO()
u = User.objects.create_user('test_username', 'test@example.com', 'testpw')
u.vpnuser.add_paid_time(timedelta(days=2), 'initial')
u = User.objects.create_user("test_username", "test@example.com", "testpw")
u.vpnuser.add_paid_time(timedelta(days=2), "initial")
u.vpnuser.save()

s = Subscription(user=u, backend_id='paypal', status='active')
s = Subscription(user=u, backend_id="paypal", status="active")
s.save()

call_command('expire_notify', stdout=out)
call_command("expire_notify", stdout=out)
self.assertEqual(len(mail.outbox), 0)

u = User.objects.get(username='test_username')
u = User.objects.get(username="test_username")
# FIXME:
# self.assertNotAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(),
# delta=timedelta(minutes=1))

def test_notify_subscription_new(self):
out = StringIO()
u = User.objects.create_user('test_username', 'test@example.com', 'testpw')
u.vpnuser.add_paid_time(timedelta(days=2), 'initial')
u = User.objects.create_user("test_username", "test@example.com", "testpw")
u.vpnuser.add_paid_time(timedelta(days=2), "initial")
u.vpnuser.last_expiry_notice = timezone.now() - timedelta(days=5)
u.vpnuser.save()

s = Subscription(user=u, backend_id='paypal', status='new')
s = Subscription(user=u, backend_id="paypal", status="new")
s.save()

call_command('expire_notify', stdout=out)
call_command("expire_notify", stdout=out)
self.assertEqual(len(mail.outbox), 1)

u = User.objects.get(username='test_username')
u = User.objects.get(username="test_username")
# FIXME:
# self.assertNotAlmostEqual(u.vpnuser.last_expiry_notice, timezone.now(),
# delta=timedelta(minutes=1))



+ 11
- 12
lambdainst/urls.py View File

@@ -4,18 +4,17 @@ import django_lcore

from . import views

app_name = 'lambdainst'
app_name = "lambdainst"

urlpatterns = [
path('login', auth_views.LoginView.as_view(), name='login'),
path('discourse_login', views.discourse_login, name='discourse_login'),
path('logout', views.logout, name='logout'),
path('signup', views.signup, name='signup'),

path('settings', views.settings, name='account_settings'),
path('config', views.config),
path('config_dl', django_lcore.views.openvpn_dl),
path('wireguard', views.wireguard),
path('wireguard/new', views.wireguard_new, name='wireguard_new'),
path('', views.index, name='index'),
path("login", auth_views.LoginView.as_view(), name="login"),
path("discourse_login", views.discourse_login, name="discourse_login"),
path("logout", views.logout, name="logout"),
path("signup", views.signup, name="signup"),
path("settings", views.settings, name="account_settings"),
path("config", views.config),
path("config_dl", django_lcore.views.openvpn_dl),
path("wireguard", views.wireguard),
path("wireguard/new", views.wireguard_new, name="wireguard_new"),
path("", views.index, name="index"),
]

+ 235
- 167
lambdainst/views.py View File

@@ -14,9 +14,12 @@ from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User
from django.db.models import Count
from django.db import transaction
from django.http import (HttpResponse,
HttpResponseNotFound, HttpResponseRedirect,
JsonResponse)
from django.http import (
HttpResponse,
HttpResponseNotFound,
HttpResponseRedirect,
JsonResponse,
)
from django.shortcuts import redirect, render
from django.utils import timezone
from django.utils.translation import ugettext as _
@@ -33,16 +36,18 @@ from . import graphs


def get_locations():
""" Pretty bad thing that returns get_locations() with translated stuff
"""Pretty bad thing that returns get_locations() with translated stuff
that depends on the request
"""
countries_d = dict(countries)
locations = django_lcore.get_clusters()
for (_), loc in locations:
code = loc['country_code'].upper()
loc['country_name'] = countries_d.get(code, code)
code = loc["country_code"].upper()
loc["country_name"] = countries_d.get(code, code)

loc['load_percent'] = ceil((loc['usage'].get('net', 0) / (loc['bandwidth'] / 1e6)) * 100)
loc["load_percent"] = ceil(
(loc["usage"].get("net", 0) / (loc["bandwidth"] / 1e6)) * 100
)
locations = list(sorted(locations, key=lambda l: l[0]))
return locations

@@ -56,58 +61,61 @@ def log_errors(request, form):


def ca_crt(request):
return HttpResponse(content=project_settings.OPENVPN_CA,
content_type='application/x-x509-ca-cert')
return HttpResponse(
content=project_settings.OPENVPN_CA, content_type="application/x-x509-ca-cert"
)


def logout(request):
auth.logout(request)
return redirect('index')
return redirect("index")


def signup(request):
if request.user.is_authenticated:
return redirect('account:index')
return redirect("account:index")

if request.method != 'POST':
if request.method != "POST":
form = SignupForm()
return render(request, 'ccvpn/signup.html', dict(form=form))
return render(request, "ccvpn/signup.html", dict(form=form))

form = SignupForm(request.POST)

grr = request.POST.get('g-recaptcha-response', '')
grr = request.POST.get("g-recaptcha-response", "")
if captcha_test(grr, request):
request.session['signup_captcha_pass'] = True
elif not request.session.get('signup_captcha_pass'):
request.session["signup_captcha_pass"] = True
elif not request.session.get("signup_captcha_pass"):
messages.error(request, _("Invalid captcha. Please try again"))
return render(request, 'ccvpn/signup.html', dict(form=form))
return render(request, "ccvpn/signup.html", dict(form=form))

if not form.is_valid():
return render(request, 'ccvpn/signup.html', dict(form=form))
return render(request, "ccvpn/signup.html", dict(form=form))

user = User.objects.create_user(form.cleaned_data['username'],
form.cleaned_data['email'],
form.cleaned_data['password'])
user = User.objects.create_user(
form.cleaned_data["username"],
form.cleaned_data["email"],
form.cleaned_data["password"],
)
user.save()

try:
user.vpnuser.referrer = User.objects.get(id=request.session.get('referrer'))
user.vpnuser.referrer = User.objects.get(id=request.session.get("referrer"))
except User.DoesNotExist:
pass

user.vpnuser.campaign = request.session.get('campaign')
user.vpnuser.add_paid_time(timedelta(days=7), 'trial')
user.vpnuser.campaign = request.session.get("campaign")
user.vpnuser.add_paid_time(timedelta(days=7), "trial")

user.vpnuser.save()
user.vpnuser.lcore_sync()

user.backend = 'django.contrib.auth.backends.ModelBackend'
user.backend = "django.contrib.auth.backends.ModelBackend"
auth.login(request, user)

# invalidate that captcha
request.session['signup_captcha_pass'] = False
request.session["signup_captcha_pass"] = False

return redirect('account:index')
return redirect("account:index")


@login_required
@@ -118,70 +126,73 @@ def discourse_login(request):
if project_settings.DISCOURSE_SSO is not True:
return HttpResponseNotFound()

payload = request.GET.get('sso', '')
signature = request.GET.get('sig', '')
payload = request.GET.get("sso", "")
signature = request.GET.get("sig", "")

expected_signature = hmac.new(sso_secret.encode('utf-8'),
payload.encode('utf-8'),
sha256).hexdigest()
expected_signature = hmac.new(
sso_secret.encode("utf-8"), payload.encode("utf-8"), sha256
).hexdigest()

if signature != expected_signature:
return HttpResponseNotFound()

if request.method == 'POST' and 'email' in request.POST:
if request.method == "POST" and "email" in request.POST:
form = ReqEmailForm(request.POST)
if not form.is_valid():
return render(request, 'ccvpn/require_email.html', dict(form=form))
return render(request, "ccvpn/require_email.html", dict(form=form))

request.user.email = form.cleaned_data['email']
request.user.email = form.cleaned_data["email"]
request.user.save()

if not request.user.email:
form = ReqEmailForm()
return render(request, 'ccvpn/require_email.html', dict(form=form))
return render(request, "ccvpn/require_email.html", dict(form=form))

try:
payload = base64.b64decode(payload).decode('utf-8')
payload = base64.b64decode(payload).decode("utf-8")
payload_data = dict(parse_qsl(payload))
except (TypeError, ValueError):
return HttpResponseNotFound()

payload_data.update({
'external_id': request.user.id,
'username': request.user.username,
'email': request.user.email,
'require_activation': 'true',
})
payload_data.update(
{
"external_id": request.user.id,
"username": request.user.username,
"email": request.user.email,
"require_activation": "true",
}
)

payload = urlencode(payload_data)
payload = base64.b64encode(payload.encode('utf-8'))
signature = hmac.new(sso_secret.encode('utf-8'), payload, sha256).hexdigest()
payload = base64.b64encode(payload.encode("utf-8"))
signature = hmac.new(sso_secret.encode("utf-8"), payload, sha256).hexdigest()
redirect_query = urlencode(dict(sso=payload, sig=signature))
redirect_path = '/session/sso_login?' + redirect_query
redirect_path = "/session/sso_login?" + redirect_query

return HttpResponseRedirect(discourse_url + redirect_path)


@login_required
def index(request):
ref_url = project_settings.ROOT_URL + '?ref=' + str(request.user.id)
ref_url = project_settings.ROOT_URL + "?ref=" + str(request.user.id)

twitter_url = 'https://twitter.com/intent/tweet?'
twitter_url = "https://twitter.com/intent/tweet?"
twitter_args = {
'text': _("Awesome VPN! 3€ per month, with a free 7 days trial!"),
'via': 'CCrypto_VPN',
'url': ref_url,
'related': 'CCrypto_VPN,CCrypto_org'
"text": _("Awesome VPN! 3€ per month, with a free 7 days trial!"),
"via": "CCrypto_VPN",
"url": ref_url,
"related": "CCrypto_VPN,CCrypto_org",
}

class price_fn:
""" Clever hack to get the price in templates with {{price.3}} with
"""Clever hack to get the price in templates with {{price.3}} with
3 an arbitrary number of months
"""

def __getitem__(self, months):
n = int(months) * get_price_float()
c = project_settings.PAYMENTS_CURRENCY[1]
return '%.2f %s' % (n, c)
return "%.2f %s" % (n, c)

context = dict(
title=_("Account"),
@@ -189,15 +200,16 @@ def index(request):
twitter_link=twitter_url + urlencode(twitter_args),
subscription=request.user.vpnuser.get_subscription(),
backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_display_name),
subscr_backends=sorted((b for b in ACTIVE_BACKENDS.values()
if b.backend_has_recurring),
key=lambda x: x.backend_id),
default_backend='paypal',
subscr_backends=sorted(
(b for b in ACTIVE_BACKENDS.values() if b.backend_has_recurring),
key=lambda x: x.backend_id,
),
default_backend="paypal",
hcaptcha_site_key=project_settings.HCAPTCHA_SITE_KEY,
price=price_fn(),
user_motd=site_config.MOTD_USER,
)
return render(request, 'lambdainst/account.html', context)
return render(request, "lambdainst/account.html", context)


def captcha_test(grr, request):
@@ -206,18 +218,17 @@ def captcha_test(grr, request):
if not project_settings.HCAPTCHA_SITE_KEY:
return True

if api_url == 'TEST' and grr == 'TEST-TOKEN':
if api_url == "TEST" and grr == "TEST-TOKEN":
# FIXME: i'm sorry.
return True

data = dict(secret=project_settings.HCAPTCHA_SECRET_KEY,
response=grr)
data = dict(secret=project_settings.HCAPTCHA_SECRET_KEY, response=grr)

try:
r = requests.post(api_url, data=data)
r.raise_for_status()
d = r.json()
return d.get('success')
return d.get("success")
except (requests.ConnectionError, requests.HTTPError, ValueError):
return False

@@ -233,13 +244,29 @@ def make_export_zip(user, name):
gw_cache = {}

def process_wg_peer(item):
keys = {"gateway_port", "id", "local_ipv4", "local_ipv6", "name", "object",
"private_key", "public_key"}
keys = {
"gateway_port",
"id",
"local_ipv4",
"local_ipv6",
"name",
"object",
"private_key",
"public_key",
}
return {k: v for (k, v) in item.items() if k in keys}

def process_ovpn_sess(item):
keys = {"connect_date", "disconnect_date", "remote", "object", "protocol", "id",
"stats", "tunnel"}
keys = {
"connect_date",
"disconnect_date",
"remote",
"object",
"protocol",
"id",
"stats",
"tunnel",
}

def convert(v):
if isinstance(v, datetime):
@@ -277,41 +304,50 @@ def make_export_zip(user, name):
return obj

with z.open(name + "/account.json", "w") as jf:
jf.write(json.dumps({
"username": user.username,
"email": user.email,
"date_joined": user.date_joined.isoformat(),
"expiration": user.vpnuser.expiration.isoformat() if user.vpnuser.expiration else None,
}, indent=2).encode('ascii'))
jf.write(
json.dumps(
{
"username": user.username,
"email": user.email,
"date_joined": user.date_joined.isoformat(),
"expiration": user.vpnuser.expiration.isoformat()
if user.vpnuser.expiration
else None,
},
indent=2,
).encode("ascii")
)

with z.open(name + "/wireguard_peers.json", "w") as jf:
try:
keys = list(map(process_wg_peer, django_lcore.api.get_wg_peers(user.username)))
keys = list(
map(process_wg_peer, django_lcore.api.get_wg_peers(user.username))
)
except lcoreapi.APINotFoundError:
keys = []
jf.write(json.dumps(keys, indent=2).encode('ascii'))
jf.write(json.dumps(keys, indent=2).encode("ascii"))

with z.open(name + "/openvpn_logs.json", "w") as jf:
base = django_lcore.api.info['current_instance']
next_page = '/users/' + user.username + '/sessions/'
base = django_lcore.api.info["current_instance"]
next_page = "/users/" + user.username + "/sessions/"
try:
items = django_lcore.api.get(base + next_page).list_iter()
except lcoreapi.APINotFoundError:
items = []
items = list(map(process_ovpn_sess, items))
jf.write(json.dumps(items, indent=2).encode('ascii'))
jf.write(json.dumps(items, indent=2).encode("ascii"))

with z.open(name + "/payments.json", "w") as jf:
items = user.payment_set.all()
items = list(map(process_payments, items))
jf.write(json.dumps(items, indent=2).encode('ascii'))
jf.write(json.dumps(items, indent=2).encode("ascii"))
z.close()
return f.getvalue()


def deactivate_user(user):
""" clear most information from a user, keeping the username and id """
"""clear most information from a user, keeping the username and id"""
user.vpnuser.clear_fields()
user.vpnuser.save()

@@ -330,33 +366,37 @@ def deactivate_user(user):
def settings(request):
can_delete = request.user.vpnuser.get_subscription() is None

if request.method != 'POST':
return render(request, 'lambdainst/settings.html', dict(
can_delete=can_delete,
))
if request.method != "POST":
return render(
request,
"lambdainst/settings.html",
dict(
can_delete=can_delete,
),
)

action = request.POST.get('action')
action = request.POST.get("action")

current_pw = request.POST.get('current_password')
current_pw = request.POST.get("current_password")
if not request.user.check_password(current_pw):
messages.error(request, _("Invalid password"))
return redirect('lambdainst:account_settings')
return redirect("lambdainst:account_settings")

if action == 'email':
email = request.POST.get('email')
if action == "email":
email = request.POST.get("email")
if email:
request.user.email = email
messages.success(request, _("OK! Email address changed."))
else:
request.user.email = ''
request.user.email = ""
messages.success(request, _("OK! Email address unset."))

request.user.save()
return redirect('lambdainst:account_settings')
return redirect("lambdainst:account_settings")

elif action == 'password':
pw = request.POST.get('password')
pw2 = request.POST.get('password2')
elif action == "password":
pw = request.POST.get("password")
pw2 = request.POST.get("password2")
if pw != pw2 or not pw:
messages.error(request, _("Password and confirmation do not match"))
else:
@@ -364,95 +404,120 @@ def settings(request):
messages.success(request, _("OK! Password changed."))
request.user.save()
django_lcore.sync_user(request.user.vpnuser)
return redirect('lambdainst:account_settings')
return redirect("lambdainst:account_settings")

elif action == 'export':
elif action == "export":
timestamp = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
data = make_export_zip(request.user, timestamp)
r = HttpResponse(content=data, content_type="application/zip")
r["Content-Disposition"] = 'attachment; filename="ccvpn-export-{}-{}.zip"'.format(request.user.username, timestamp)
r[
"Content-Disposition"
] = 'attachment; filename="ccvpn-export-{}-{}.zip"'.format(
request.user.username, timestamp
)
return r

elif action == 'delete' and can_delete:
elif action == "delete" and can_delete:
with transaction.atomic():
deactivate_user(request.user)
logout(request)
messages.success(request, _("OK! Your account has been deactivated."))
return redirect('/')

return render(request, 'lambdainst/settings.html', dict(
title=_("Settings"),
user=request.user,
can_delete=can_delete,
))
return redirect("/")

return render(
request,
"lambdainst/settings.html",
dict(
title=_("Settings"),
user=request.user,
can_delete=can_delete,
),
)


@login_required
def config(request):
return render(request, 'lambdainst/config.html', dict(
title=_("Config"),
config_os=django_lcore.openvpn.CONFIG_OS,
config_countries=(c for _, c in get_locations()),
config_protocols=django_lcore.openvpn.PROTOCOLS,
))
return render(
request,
"lambdainst/config.html",
dict(
title=_("Config"),
config_os=django_lcore.openvpn.CONFIG_OS,
config_countries=(c for _, c in get_locations()),
config_protocols=django_lcore.openvpn.PROTOCOLS,
),
)


def api_locations(request):
def format_loc(cc, l):
msg = ""
tags = l.get('tags', {})
message = tags.get('message')
tags = l.get("tags", {})
message = tags.get("message")
if message:
msg = " [%s]" % message
return {
'country_name': l['country_name'] + msg,
'country_code': cc,
'hostname': l['hostname'],
'bandwidth': l['bandwidth'],
'servers': l['servers'],
"country_name": l["country_name"] + msg,
"country_code": cc,
"hostname": l["hostname"],
"bandwidth": l["bandwidth"],
"servers": l["servers"],
}
return JsonResponse(dict(locations=[format_loc(cc, l) for cc, l in get_locations()]))

return JsonResponse(
dict(locations=[format_loc(cc, l) for cc, l in get_locations()])
)


def status(request):
locations = get_locations()

ctx = {
'title': _("Servers"),
'locations': locations,
"title": _("Servers"),
"locations": locations,
}
return render(request, 'lambdainst/status.html', ctx)
return render(request, "lambdainst/status.html", ctx)


@user_passes_test(lambda user: user.is_staff)
def admin_status(request):
graph_name = request.GET.get('graph_name')
graph_period = request.GET.get('period')
if graph_period not in ('y', 'm'):
graph_period = 'm'
graph_name = request.GET.get("graph_name")
graph_period = request.GET.get("period")
if graph_period not in ("y", "m"):
graph_period = "m"
if graph_name:
if graph_name == 'users':
if graph_name == "users":
content = graphs.users_graph(graph_period)
elif graph_name == 'payments_paid':
elif graph_name == "payments_paid":
content = graphs.payments_paid_graph(graph_period)
elif graph_name == 'payments_success':
elif graph_name == "payments_success":
content = graphs.payments_success_graph(graph_period)
else:
return HttpResponseNotFound()
return HttpResponse(content=content, content_type='image/svg+xml')
return HttpResponse(content=content, content_type="image/svg+xml")

payment_status = ((b, b.get_info()) for b in ACTIVE_BACKENDS.values())
payment_status = ((b, i) for (b, i) in payment_status if i)

lcore_keys = {'core_name', 'core_now', 'core_version', 'current_instance', 'key_public'}
lcore_keys = {
"core_name",
"core_now",
"core_version",
"current_instance",
"key_public",
}

ctx = {
'api_status': {k: str(v) for k, v in django_lcore.api.info.items() if k in lcore_keys},
'payment_backends': sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id),
'payment_status': payment_status,
"api_status": {
k: str(v) for k, v in django_lcore.api.info.items() if k in lcore_keys
},
"payment_backends": sorted(
ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id
),
"payment_status": payment_status,
}
ctx.update(site.each_context(request))
return render(request, 'lambdainst/admin_status.html', ctx)
return render(request, "lambdainst/admin_status.html", ctx)


@user_passes_test(lambda user: user.is_staff)
@@ -460,36 +525,40 @@ def admin_ref(request):
last_week = datetime.now() - timedelta(days=7)
last_month = datetime.now() - timedelta(days=30)

top_ref = User.objects.annotate(n_ref=Count('referrals')).order_by('-n_ref')[:10]
top_ref_week = User.objects.filter(referrals__user__date_joined__gt=last_week) \
.annotate(n_ref=Count('referrals')) \
.order_by('-n_ref')[:10]
top_ref_month = User.objects.filter(referrals__user__date_joined__gt=last_month) \
.annotate(n_ref=Count('referrals')) \
.order_by('-n_ref')[:10]
top_ref = User.objects.annotate(n_ref=Count("referrals")).order_by("-n_ref")[:10]
top_ref_week = (
User.objects.filter(referrals__user__date_joined__gt=last_week)
.annotate(n_ref=Count("referrals"))
.order_by("-n_ref")[:10]
)
top_ref_month = (
User.objects.filter(referrals__user__date_joined__gt=last_month)
.annotate(n_ref=Count("referrals"))
.order_by("-n_ref")[:10]
)

ctx = {
'top_ref': top_ref,
'top_ref_week': top_ref_week,
'top_ref_month': top_ref_month,
"top_ref": top_ref,
"top_ref_week": top_ref_week,
"top_ref_month": top_ref_month,
}
ctx.update(site.each_context(request))
return render(request, 'lambdainst/admin_ref.html', ctx)
return render(request, "lambdainst/admin_ref.html", ctx)


@login_required
def wireguard(request):
api = django_lcore.api
if request.method == 'POST':
action = request.POST.get('action')
if action == 'delete_key':
key = api.get_wg_peer(request.user.username, request.POST.get('peer_id'))
if request.method == "POST":
action = request.POST.get("action")
if action == "delete_key":
key = api.get_wg_peer(request.user.username, request.POST.get("peer_id"))
if key:
key.delete()
elif action == 'set_name':
key = api.get_wg_peer(request.user.username, request.POST.get('peer_id'))
elif action == "set_name":
key = api.get_wg_peer(request.user.username, request.POST.get("peer_id"))
if key:
name = request.POST.get('name')
name = request.POST.get("name")
if name:
name = name[:21]
key.rename(name)
@@ -503,19 +572,19 @@ def wireguard(request):

context = dict(
can_create_key=len(keys) < int(site_config.WIREGUARD_MAX_PEERS),
menu_item='wireguard',
menu_item="wireguard",
enabled=request.user.vpnuser.is_paid,
config_countries=[(k, v) for (k, v) in django_lcore.get_clusters()],
keys=keys,
locations=get_locations(),
)
return render(request, 'lambdainst/wireguard.html', context)
return render(request, "lambdainst/wireguard.html", context)


@login_required
def wireguard_new(request):
if not request.user.vpnuser.is_paid:
return redirect('account:index')
return redirect("account:index")

try:
keys = django_lcore.api.get_wg_peers(request.user.username)
@@ -523,25 +592,24 @@ def wireguard_new(request):
django_lcore.sync_user(request.user.vpnuser)
keys = []
if len(keys) >= int(site_config.WIREGUARD_MAX_PEERS):
return redirect('/account/wireguard')
return redirect("/account/wireguard")

api = django_lcore.api
if request.method == 'POST':
action = request.POST.get('action')
if request.method == "POST":
action = request.POST.get("action")
form = WgPeerForm(request.POST)
if action == 'add_key':
if action == "add_key":
if form.is_valid():
api.create_wg_peer(
request.user.username,
public_key=form.cleaned_data['public_key'],
name=form.cleaned_data['name'],
public_key=form.cleaned_data["public_key"],
name=form.cleaned_data["name"],
)
else:
log_errors(request, form)
return redirect('/account/wireguard')
return redirect("/account/wireguard")
context = dict(
menu_item='wireguard',
menu_item="wireguard",
locations=get_locations(),
)
return render(request, 'lambdainst/wireguard_new.html', context)

return render(request, "lambdainst/wireguard_new.html", context)

+ 43
- 40
poetry.lock View File

@@ -71,25 +71,27 @@ python-versions = "*"

[[package]]
name = "black"
version = "20.8b1"
version = "21.7b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.6.2"

[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.6,<1"
pathspec = ">=0.8.1,<1"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = ">=1.4.0"
typing-extensions = ">=3.7.4"
tomli = ">=0.2.6,<2.0.0"
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""}
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}

[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
python2 = ["typed-ast (>=1.4.2)"]
uvloop = ["uvloop (>=0.15.2)"]

[[package]]
name = "cached-property"
@@ -408,18 +410,18 @@ jsmin = "*"

[[package]]
name = "flower"
version = "0.9.5"
version = "1.0.0"
description = "Celery Flower"
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
celery = {version = ">=4.3.0", markers = "python_version >= \"3.7\""}
celery = ">=5.0.5"
humanize = "*"
prometheus-client = "0.8.0"
prometheus-client = ">=0.8.0"
pytz = "*"
tornado = {version = ">=5.0.0,<7.0.0", markers = "python_version >= \"3.5.2\""}
tornado = ">=5.0.0,<7.0.0"

[[package]]
name = "humanize"
@@ -499,16 +501,17 @@ python-versions = "*"

[[package]]
name = "isort"
version = "5.8.0"
version = "5.9.2"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
python-versions = ">=3.6.1,<4.0"

[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]

[[package]]
name = "jedi"
@@ -682,22 +685,22 @@ python-versions = "*"

[[package]]
name = "prometheus-client"
version = "0.8.0"
version = "0.11.0"
description = "Python client for the Prometheus monitoring system."