You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

384 lines
12 KiB
Python

import requests
import io
import zipfile
from urllib.parse import urlencode
from datetime import timedelta, datetime
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseForbidden
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.admin.sites import site
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.utils import timezone
from django.conf import settings as project_settings
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Count
from django.contrib import auth
from django.contrib.auth.models import User
from django_countries import countries
from payments.models import ACTIVE_BACKENDS
from .forms import SignupForm
from .models import GiftCode, VPNUser
from .core import core_api
from . import core
from . import graphs
from . import openvpn
def get_locations():
""" Pretty bad thing that returns get_locations() with translated stuff
that depends on the request
"""
countries_d = dict(countries)
locations = core.get_locations()
for k, v in locations:
cc = v['country_code'].upper()
v['country_name'] = countries_d.get(cc, cc)
return locations
def ca_crt(request):
return HttpResponse(content=project_settings.OPENVPN_CA,
content_type='application/x-x509-ca-cert')
def logout(request):
auth.logout(request)
return redirect('index')
def signup(request):
if request.user.is_authenticated():
return redirect('account:index')
if request.method != 'POST':
form = SignupForm()
return render(request, 'ccvpn/signup.html', dict(form=form))
form = SignupForm(request.POST)
if not form.is_valid():
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.save()
if core.VPN_AUTH_STORAGE == 'core':
core.create_user(form.cleaned_data['username'], form.cleaned_data['password'])
try:
user.vpnuser.referrer = User.objects.get(id=request.session.get('referrer'))
except User.DoesNotExist:
pass
user.vpnuser.save()
user.backend = 'django.contrib.auth.backends.ModelBackend'
auth.login(request, user)
return redirect('account:index')
@login_required
def index(request):
ref_url = project_settings.ROOT_URL + '?ref=' + str(request.user.id)
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'
}
context = dict(
title=_("Account"),
ref_url=ref_url,
twitter_link=twitter_url + urlencode(twitter_args),
backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id),
default_backend='paypal',
recaptcha_site_key=project_settings.RECAPTCHA_SITE_KEY,
)
return render(request, 'lambdainst/account.html', context)
def captcha_test(grr, request):
api_url = project_settings.RECAPTCHA_API
if api_url == 'TEST' and grr == 'TEST-TOKEN':
# FIXME: i'm sorry.
return True
data = dict(secret=project_settings.RECAPTCHA_SECRET_KEY,
remoteip=request.META['REMOTE_ADDR'],
response=grr)
try:
r = requests.post(api_url, data=data)
r.raise_for_status()
d = r.json()
return d.get('success')
except (requests.ConnectionError, requests.HTTPError, ValueError):
return False
@login_required
def trial(request):
if request.method != 'POST' or not request.user.vpnuser.can_have_trial:
return redirect('account:index')
grr = request.POST.get('g-recaptcha-response', '')
if captcha_test(grr, request):
request.user.vpnuser.give_trial_period()
request.user.vpnuser.save()
messages.success(request, _("OK!"))
else:
messages.error(request, _("Invalid captcha"))
return redirect('account:index')
@login_required
def settings(request):
if request.method != 'POST':
return render(request, 'lambdainst/settings.html')
pw = request.POST.get('password')
pw2 = request.POST.get('password2')
if pw and pw2:
if pw != pw2:
messages.error(request, _("Passwords do not match"))
else:
request.user.set_password(pw)
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_password(request.user, pw)
messages.success(request, _("OK!"))
email = request.POST.get('email')
if email:
request.user.email = email
else:
request.user.email = ''
request.user.save()
return render(request, 'lambdainst/settings.html', dict(title=_("Settings")))
@login_required
def gift_code(request):
try:
code = GiftCode.objects.get(code=request.POST.get('code', '').strip(), available=True)
except GiftCode.DoesNotExist:
code = None
if code is None:
messages.error(request, _("Gift code not found or already used."))
elif not code.use_on(request.user):
messages.error(request, _("Gift code only available to free accounts."))
else:
messages.success(request, _("OK!"))
return redirect('account:index')
@login_required
def logs(request):
page_size = 20
page = int(request.GET.get('page', 0))
offset = page * page_size
base = core_api.info['current_instance']
path = '/users/' + request.user.username + '/sessions/'
l = core_api.get(base + path, offset=offset, limit=page_size)
return render(request, 'lambdainst/logs.html', {
'sessions': l['items'],
'page': page,
'prev': page - 1 if page > 0 else None,
'next': page + 1 if offset + page_size < l['total_count'] else None,
'last_page': l['total_count'] // page_size,
'title': _("Logs"),
})
@login_required
def config(request):
return render(request, 'lambdainst/config.html', dict(
titla=_("Config"),
config_os=openvpn.CONFIG_OS,
config_countries=(c for _, c in get_locations()),
config_protocols=openvpn.PROTOCOLS,
))
@login_required
def config_dl(request):
allowed_cc = [cc for (cc, _) in get_locations()]
common_options = {
'protocol': request.GET.get('protocol'),
'os': request.GET.get('client_os'),
'http_proxy': request.GET.get('http_proxy'),
'ipv6': 'enable_ipv6' in request.GET,
}
# Should be validated since it's used in the filename
# other common options are only put in the config file
protocol = common_options['protocol']
if protocol not in ('udp', 'udpl', 'tcp'):
return HttpResponseNotFound()
location = request.GET.get('gateway')
if location == 'all':
# Multiple gateways in a zip archive
f = io.BytesIO()
z = zipfile.ZipFile(f, mode='w')
for gw_name in allowed_cc + ['random']:
filename = 'ccrypto-%s-%s.ovpn' % (gw_name, protocol)
config = openvpn.make_config(gw_name=gw_name, **common_options)
z.writestr(filename, config.encode('utf-8'))
z.close()
r = HttpResponse(content=f.getvalue(), content_type='application/zip')
r['Content-Disposition'] = 'attachment; filename="%s.zip"' % filename
return r
else:
# Single gateway
if location[3:] in allowed_cc:
gw_name = location[3:]
else:
gw_name = 'random'
filename = 'ccrypto-%s-%s.ovpn' % (gw_name, protocol)
config = openvpn.make_config(gw_name=gw_name, **common_options)
if 'plain' in request.GET:
return HttpResponse(content=config, content_type='text/plain')
else:
r = HttpResponse(content=config, content_type='application/x-openvpn-profile')
r['Content-Disposition'] = 'attachment; filename="%s.ovpn"' % filename
return r
@csrf_exempt
def api_auth(request):
if request.method != 'POST':
return HttpResponseNotFound()
if core.VPN_AUTH_STORAGE != 'inst':
return HttpResponseNotFound()
username = request.POST.get('username')
password = request.POST.get('password')
secret = request.POST.get('secret')
if secret != core.LCORE_INST_SECRET:
return HttpResponseForbidden(content="Invalid secret")
user = authenticate(username=username, password=password)
if not user or not user.is_active:
return JsonResponse(dict(status='fail', message="Invalid credentials"))
if not user.vpnuser.is_paid:
return JsonResponse(dict(status='fail', message="Not allowed to connect"))
user.vpnuser.last_vpn_auth = timezone.now()
user.vpnuser.save()
return JsonResponse(dict(status='ok'))
def api_locations(request):
def format_loc(cc, l):
return {
'country_name': l['country_name'],
'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()]))
def status(request):
locations = get_locations()
ctx = {
'title': _("Status"),
'n_users': VPNUser.objects.filter(expiration__gte=timezone.now()).count(),
'n_sess': core.current_active_sessions(),
'n_gws': sum(l['servers'] for cc, l in locations),
'n_countries': len(set(cc for cc, l in locations)),
'total_bw': sum(l['bandwidth'] for cc, l in locations),
'locations': locations,
}
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'
if graph_name:
if graph_name == 'users':
content = graphs.users_graph(graph_period)
elif graph_name == 'payments_paid':
content = graphs.payments_paid_graph(graph_period)
elif graph_name == 'payments_success':
content = graphs.payments_success_graph(graph_period)
else:
return HttpResponseNotFound()
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)
ctx = {
'api_status': {k: str(v) for k, v in core_api.info.items()},
'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)
@user_passes_test(lambda user: user.is_staff)
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]
ctx = {
'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)