import requests import io import zipfile import hmac import base64 from hashlib import sha256 from urllib.parse import urlencode, parse_qsl from datetime import timedelta, datetime from django.http import ( HttpResponse, JsonResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden ) 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, ReqEmailForm 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 discourse_login(request): sso_secret = project_settings.DISCOURSE_SECRET discourse_url = project_settings.DISCOURSE_URL if project_settings.DISCOURSE_SSO is not True: return HttpResponseNotFound() 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() if signature != expected_signature: return HttpResponseNotFound() 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)) 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)) try: 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 = urlencode(payload_data) 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 return HttpResponseRedirect(discourse_url + redirect_path) @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' } class price_fn: """ 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) * project_settings.PAYMENTS_MONTHLY_PRICE / 100 c = project_settings.PAYMENTS_CURRENCY[1] return '%.2f %s' % (n, c) context = dict( title=_("Account"), ref_url=ref_url, twitter_link=twitter_url + urlencode(twitter_args), subscription=request.user.vpnuser.get_subscription(include_unconfirmed=True), backends=sorted(ACTIVE_BACKENDS.values(), key=lambda x: x.backend_id), subscr_backends=sorted((b for b in ACTIVE_BACKENDS.values() if b.backend_has_recurring), key=lambda x: x.backend_id), default_backend='paypal', recaptcha_site_key=project_settings.RECAPTCHA_SITE_KEY, price=price_fn(), ) 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( title=_("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)