|
|
|
from datetime import timedelta, datetime
|
|
|
|
import lcoreapi
|
|
|
|
from django.conf import settings
|
|
|
|
import logging
|
|
|
|
|
|
|
|
cluster_messages = settings.LAMBDAINST_CLUSTER_MESSAGES
|
|
|
|
|
|
|
|
lcore_settings = settings.LCORE
|
|
|
|
|
|
|
|
LCORE_BASE_URL = lcore_settings.get('BASE_URL')
|
|
|
|
LCORE_API_KEY = lcore_settings['API_KEY']
|
|
|
|
LCORE_API_SECRET = lcore_settings['API_SECRET']
|
|
|
|
LCORE_SOURCE_ADDR = lcore_settings.get('SOURCE_ADDRESS')
|
|
|
|
LCORE_INST_SECRET = lcore_settings['INST_SECRET']
|
|
|
|
LCORE_TIMEOUT = lcore_settings.get('TIMEOUT', 10)
|
|
|
|
|
|
|
|
# The default is to log the exception and only raise it if we cannot show
|
|
|
|
# the previous value or a default value instead.
|
|
|
|
LCORE_RAISE_ERRORS = bool(lcore_settings.get('RAISE_ERRORS', False))
|
|
|
|
|
|
|
|
LCORE_CACHE_TTL = lcore_settings.get('CACHE_TTL', 60)
|
|
|
|
if isinstance(LCORE_CACHE_TTL, int):
|
|
|
|
LCORE_CACHE_TTL = timedelta(seconds=LCORE_CACHE_TTL)
|
|
|
|
assert isinstance(LCORE_CACHE_TTL, timedelta)
|
|
|
|
|
|
|
|
VPN_AUTH_STORAGE = settings.VPN_AUTH_STORAGE
|
|
|
|
assert VPN_AUTH_STORAGE in ('core', 'inst')
|
|
|
|
|
|
|
|
core_api = lcoreapi.API(LCORE_API_KEY, LCORE_API_SECRET, LCORE_BASE_URL,
|
|
|
|
timeout=LCORE_TIMEOUT)
|
|
|
|
|
|
|
|
|
|
|
|
class APICache:
|
|
|
|
""" Cache data for a time, try to update and silence errors.
|
|
|
|
Outdated data is not a problem.
|
|
|
|
"""
|
|
|
|
def __init__(self, ttl=None, initial=None):
|
|
|
|
self.cache_date = datetime.fromtimestamp(0)
|
|
|
|
self.ttl = ttl or LCORE_CACHE_TTL
|
|
|
|
|
|
|
|
self.has_cached_value = initial is not None
|
|
|
|
self.cached = initial() if initial else None
|
|
|
|
|
|
|
|
def query(self, wrapped, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return wrapped(*args, **kwargs)
|
|
|
|
except lcoreapi.APIError:
|
|
|
|
logger = logging.getLogger('django.request')
|
|
|
|
logger.exception("core api error")
|
|
|
|
|
|
|
|
if LCORE_RAISE_ERRORS:
|
|
|
|
raise
|
|
|
|
|
|
|
|
if not self.has_cached_value:
|
|
|
|
# We only return a default value if we were given one.
|
|
|
|
# Prevents returning an unexpected None.
|
|
|
|
raise
|
|
|
|
|
|
|
|
# Return previous value
|
|
|
|
return self.cached
|
|
|
|
|
|
|
|
def __call__(self, wrapped):
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
if self.cache_date > (datetime.now() - self.ttl):
|
|
|
|
return self.cached
|
|
|
|
|
|
|
|
self.cached = self.query(wrapped, *args, **kwargs)
|
|
|
|
|
|
|
|
# New results *and* errors are cached
|
|
|
|
self.cache_date = datetime.now()
|
|
|
|
return self.cached
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
@APICache(initial=lambda: 0)
|
|
|
|
def current_active_sessions():
|
|
|
|
return core_api.get(core_api.info['current_instance'] + '/sessions', active=True)['total_count']
|
|
|
|
|
|
|
|
|
|
|
|
@APICache(initial=lambda: [])
|
|
|
|
def get_locations():
|
|
|
|
gateways = core_api.get('gateways/', enabled=True)
|
|
|
|
locations = {}
|
|
|
|
|
|
|
|
for gw in gateways.list_iter():
|
|
|
|
cc = gw['cluster_name']
|
|
|
|
|
|
|
|
if cc not in locations:
|
|
|
|
locations[cc] = dict(
|
|
|
|
servers=0,
|
|
|
|
bandwidth=0,
|
|
|
|
hostname='gw.' + cc + '.204vpn.net',
|
|
|
|
country_code=cc,
|
|
|
|
message=cluster_messages.get(cc),
|
|
|
|
)
|
|
|
|
|
|
|
|
locations[cc]['servers'] += 1
|
|
|
|
locations[cc]['bandwidth'] += gw['bandwidth'] or 0
|
|
|
|
|
|
|
|
locations = sorted(locations.items(), key=lambda x: x[1]['country_code'])
|
|
|
|
return locations
|
|
|
|
|
|
|
|
|
|
|
|
@APICache(initial=lambda: [])
|
|
|
|
def get_gateway_exit_ips():
|
|
|
|
gateways = core_api.get('gateways/', enabled=True)
|
|
|
|
ipv4_list = []
|
|
|
|
ipv6_list = []
|
|
|
|
|
|
|
|
for gw in gateways.list_iter():
|
|
|
|
ma = gw['main_addr']
|
|
|
|
if ma.get('ipv4'):
|
|
|
|
ipv4_list.append(ma['ipv4'])
|
|
|
|
if ma.get('ipv6'):
|
|
|
|
ipv6_list.append(ma['ipv6'])
|
|
|
|
|
|
|
|
# TODO: IPv6 support
|
|
|
|
|
|
|
|
return ipv4_list
|
|
|
|
|
|
|
|
|
|
|
|
def is_vpn_gateway(ip):
|
|
|
|
addresses = get_gateway_exit_ips()
|
|
|
|
return ip in addresses
|
|
|
|
|
|
|
|
|
|
|
|
def create_user(username, cleartext_password):
|
|
|
|
""" The password will be hashed and stored safely on the core,
|
|
|
|
so we have to send it clearly here.
|
|
|
|
"""
|
|
|
|
path = core_api.info['current_instance'] + '/users/'
|
|
|
|
core_api.post(path, data={
|
|
|
|
'username': username,
|
|
|
|
'password': cleartext_password,
|
|
|
|
'expiration_date': datetime(1, 1, 1).isoformat(), # Expired.
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
def update_user_expiration(user):
|
|
|
|
path = core_api.info['current_instance'] + '/users/' + user.username
|
|
|
|
|
|
|
|
try:
|
|
|
|
if not user.is_active:
|
|
|
|
core_api.patch(path, data={
|
|
|
|
'expiration_date': datetime(1, 1, 1).isoformat(), # Expired.
|
|
|
|
})
|
|
|
|
return
|
|
|
|
|
|
|
|
core_api.patch(path, data={
|
|
|
|
'expiration_date': user.vpnuser.expiration,
|
|
|
|
})
|
|
|
|
except lcoreapi.APIError:
|
|
|
|
# User can't do anything to this, we should just report it
|
|
|
|
logger = logging.getLogger('django.request')
|
|
|
|
logger.exception("core api error, missing user (exp update)")
|
|
|
|
|
|
|
|
|
|
|
|
def update_user_password(user, cleartext_password):
|
|
|
|
path = core_api.info['current_instance'] + '/users/' + user.username
|
|
|
|
|
|
|
|
try:
|
|
|
|
core_api.patch(path, data={
|
|
|
|
'password': cleartext_password,
|
|
|
|
})
|
|
|
|
except lcoreapi.APINotFoundError:
|
|
|
|
# This time we can try fix it!
|
|
|
|
create_user(user.username, cleartext_password)
|
|
|
|
except lcoreapi.APIError:
|
|
|
|
# and maybe fail.
|
|
|
|
logger = logging.getLogger('django.request')
|
|
|
|
logger.exception("core api error (password update)")
|
|
|
|
|
|
|
|
|
|
|
|
def delete_user(username):
|
|
|
|
path = core_api.info['current_instance'] + '/users/' + username
|
|
|
|
core_api.delete(path)
|
|
|
|
|