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)