diff --git a/circle/dashboard/models.py b/circle/dashboard/models.py index c5fe0a3..91bd278 100644 --- a/circle/dashboard/models.py +++ b/circle/dashboard/models.py @@ -44,7 +44,7 @@ from common.models import HumanReadableObject, create_readable, Encoder from vm.tasks.agent_tasks import add_keys, del_keys -from dashboard import store_api +from .store_api import Store logger = getLogger(__name__) @@ -213,12 +213,10 @@ def create_profile(sender, user, request, **kwargs): return False profile, created = Profile.objects.get_or_create(user=user) - if created: - user_home = "u-%d" % user.pk - if not store_api.userexist(user_home): - store_api.createuser(user_home, profile.smb_password, None, - profile.disk_quota) - + try: + Store(user).create_user(profile.smb_password, None, profile.disk_quota) + except: + logger.exception("Can't create user %s", unicode(user)) return created user_logged_in.connect(create_profile) diff --git a/circle/dashboard/store_api.py b/circle/dashboard/store_api.py index 64dc6ee..4ec4e4f 100644 --- a/circle/dashboard/store_api.py +++ b/circle/dashboard/store_api.py @@ -1,203 +1,136 @@ -from django.http import Http404 import json import logging -import requests - +from urlparse import urljoin from datetime import datetime -from sizefield.utils import filesizeformat +from django.http import Http404 from django.conf import settings +from requests import get, post, codes +from sizefield.utils import filesizeformat logger = logging.getLogger(__name__) -class Mock(object): +class StoreApiException(Exception): pass -def get_host(): - return settings.STORE_URL - - -def get_request_arguments(): - args = {'verify': settings.STORE_VERIFY_SSL} - - if settings.STORE_SSL_AUTH: - args['cert'] = (settings.STORE_CLIENT_CERT, settings.STORE_CLIENT_KEY) - if settings.STORE_BASIC_AUTH: - args['auth'] = (settings.STORE_CLIENT_USER, - settings.STORE_CLIENT_PASSWORD) - return args - - -def post_request(url, payload, timeout=None): - try: - headers = {'content-type': 'application/json'} - r = requests.post(url, data=payload, headers=headers, timeout=timeout, - **get_request_arguments()) - return r - except Exception as e: - logger.error("Error in store POST: %s" % e) - dummy = Mock() - setattr(dummy, "status_code", 200) - setattr(dummy, "content", "[]") - return dummy - - -def get_request(url, timeout=None): - try: - headers = {'content-type': 'application/json'} - r = requests.get(url, headers=headers, timeout=timeout, - **get_request_arguments()) - return r - except Exception as e: - logger.error("Error in store GET: %s" % e) - dummy = Mock() - setattr(dummy, "status_code", 200) - setattr(dummy, "content", "[]") - return dummy - - -def listfolder(neptun, path): - url = settings.STORE_URL + '/' + neptun - payload = json.dumps({'CMD': 'LIST', 'PATH': path}) - r = post_request(url, payload, timeout=5) - if r.status_code == requests.codes.ok: - tupplelist = json.loads(r.content) - return tupplelist - else: - raise Http404 - - -def toplist(neptun): - url = settings.STORE_URL + '/' + neptun - payload = json.dumps({'CMD': 'TOPLIST'}) - r = post_request(url, payload, timeout=2) - if r.status_code == requests.codes.ok: - tupplelist = json.loads(r.content) - return tupplelist - else: - raise Http404 - - -def requestdownload(neptun, path): - url = settings.STORE_URL + '/' + neptun - payload = json.dumps({'CMD': 'DOWNLOAD', 'PATH': path}) - r = post_request(url, payload) - response = json.loads(r.content) - return response['LINK'] - - -def requestupload(neptun, path): - url = settings.STORE_URL+'/'+neptun - payload = json.dumps({'CMD': 'UPLOAD', 'PATH': path}) - r = post_request(url, payload) - response = json.loads(r.content) - print response - if r.status_code == requests.codes.ok: - return response['LINK'] - else: - raise Http404 - - -def requestremove(neptun, path): - url = settings.STORE_URL+'/'+neptun - payload = json.dumps({'CMD': 'REMOVE', 'PATH': path}) - r = post_request(url, payload) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def requestnewfolder(neptun, path): - url = settings.STORE_URL+'/'+neptun - payload = json.dumps({'CMD': 'NEW_FOLDER', 'PATH': path}) - r = post_request(url, payload) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def requestrename(neptun, old_path, new_name): - url = settings.STORE_URL+'/'+neptun - payload = json.dumps( - {'CMD': 'RENAME', 'NEW_NAME': new_name, 'PATH': old_path}) - r = post_request(url, payload) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def requestquota(neptun): - url = settings.STORE_URL+'/'+neptun - r = get_request(url) - if r.status_code == requests.codes.ok: - return json.loads(r.content) - else: - return False - - -def set_quota(neptun, quota): - url = settings.STORE_URL+'/quota/'+neptun - payload = json.dumps({'QUOTA': quota}) - r = post_request(url, payload) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def userexist(neptun): - url = settings.STORE_URL+'/'+neptun - r = get_request(url, timeout=5) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def createuser(neptun, password, key_list, quota): - url = settings.STORE_URL+'/new/'+neptun - payload = json.dumps( - {'SMBPASSWD': password, 'KEYS': key_list, 'QUOTA': quota}) - r = post_request(url, payload, timeout=5) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def updateauthorizationinfo(neptun, password, key_list): - url = settings.STORE_URL+'/set/'+neptun - payload = json.dumps({'SMBPASSWD': password, 'KEYS': key_list}) - r = post_request(url, payload) - if r.status_code == requests.codes.ok: - return True - else: - return False - - -def process_list(content): - for d in content: - d['human_readable_date'] = datetime.utcfromtimestamp(float( - d['MTIME'])) - delta = (datetime.utcnow() - d['human_readable_date']).total_seconds() - d['is_new'] = delta < 5 and delta > 0 - d['human_readable_size'] = ( - "directory" if d['TYPE'] == "D" else - filesizeformat(float(d['SIZE']))) - - if len(d['DIR']) == 1 and d['DIR'][0] == ".": - d['directory'] = "/" +class NotOkException(StoreApiException): + def __init__(self, status, *args, **kwargs): + self.status = status + super(NotOkException, self).__init__(*args, **kwargs) + + +class Store(object): + + def __init__(self, user, default_timeout=0.5): + self.request_args = {'verify': settings.STORE_VERIFY_SSL} + if settings.STORE_SSL_AUTH: + self.request_args['cert'] = (settings.STORE_CLIENT_CERT, + settings.STORE_CLIENT_KEY) + if settings.STORE_BASIC_AUTH: + self.request_args['auth'] = (settings.STORE_CLIENT_USER, + settings.STORE_CLIENT_PASSWORD) + self.username = "u-%d" % user.pk + self.default_timeout = default_timeout + self.store_url = settings.STORE_URL + + def _request(self, url, method=get, timeout=None, + raise_status_code=True, **kwargs): + url = urljoin(self.store_url, url) + if timeout is None: + timeout = self.default_timeout + payload = json.dumps(kwargs) + try: + headers = {'content-type': 'application/json'} + response = method(url, data=payload, headers=headers, + timeout=timeout, **self.request_args) + except Exception: + logger.exception("Error in store %s loading %s", + unicode(method), url) + raise else: - d['directory'] = "/" + d['DIR'] + "/" - - d['path'] = d['directory'] - d['path'] += d['NAME'] - if d['TYPE'] == "D": - d['path'] += "/" + if raise_status_code and response.status_code != codes.ok: + if response.status_code == 404: + raise Http404() + else: + raise NotOkException(response.status_code) + return response + + def _request_cmd(self, cmd, **kwargs): + return self._request(self.username, post, CMD=cmd) + + def list(self, path, process=True): + r = self._request_cmd("LIST", PATH=path) + result = r.json() + if process: + return self._process_list(result) + else: + return result - return sorted(content, key=lambda k: k['TYPE']) + def toplist(self, process=True): + r = self._request_cmd("TOPLIST") + result = r.json() + if process: + return self._process_list(result) + else: + return result + + def request_download(self, path): + r = self._request_cmd("DOWNLOAD", PATH=path) + return r.json()['LINK'] + + def request_upload(self, path): + r = self._request_cmd("UPLOAD", PATH=path) + return r.json()['LINK'] + + def remove(self, path): + self._request_cmd("REMOVE", PATH=path) + + def new_folder(self, path): + self._request_cmd("NEW_FOLDER", PATH=path) + + def rename(self, old_path, new_name): + self._request_cmd("RENAME", PATH=old_path, NEW_NAME=new_name) + + def get_quota(self): # no CMD? :o + r = self._request(self.username) + return r.json() + + def set_quota(self, quota): + self._request(self.username + "/quota/", post, QUOTA=quota) + + def user_exist(self): + try: + self._request(self.username) + return True + except NotOkException: + return False + + def create_user(self, password, keys, quota): + self._request("/new/" + self.username, SMBPASSWD=password, KEYS=keys, + QUOTA=quota) + + @staticmethod + def _process_list(content): + for d in content: + d['human_readable_date'] = datetime.utcfromtimestamp(float( + d['MTIME'])) + delta = (datetime.utcnow() - + d['human_readable_date']).total_seconds() + d['is_new'] = 0 < delta < 5 + d['human_readable_size'] = ( + "directory" if d['TYPE'] == "D" else + filesizeformat(float(d['SIZE']))) + + if len(d['DIR']) == 1 and d['DIR'][0] == ".": + d['directory'] = "/" + else: + d['directory'] = "/" + d['DIR'] + "/" + + d['path'] = d['directory'] + d['path'] += d['NAME'] + if d['TYPE'] == "D": + d['path'] += "/" + + return sorted(content, key=lambda k: k['TYPE']) diff --git a/circle/dashboard/views.py b/circle/dashboard/views.py index 2032bba..f5ebc6c 100644 --- a/circle/dashboard/views.py +++ b/circle/dashboard/views.py @@ -21,6 +21,7 @@ from __future__ import unicode_literals, absolute_import from collections import OrderedDict from itertools import chain from os import getenv +from os.path import join, normpath, dirname import os import json import logging @@ -83,7 +84,7 @@ from storage.models import Disk from firewall.models import Vlan, Host, Rule from .models import Favourite, Profile, GroupProfile, FutureMember -from dashboard import store_api +from .store_api import Store logger = logging.getLogger(__name__) saml_available = hasattr(settings, "SAML_CONFIG") @@ -225,14 +226,15 @@ class IndexView(LoginRequiredMixin, TemplateView): 'operator', user).all()[:5] # toplist - user_home = "u-%d" % user.pk - cache_key = "toplist-%s" % user_home + cache_key = "toplist-%d" % self.request.user.pk cache = get_cache("default") toplist = cache.get(cache_key) if not toplist: try: - toplist = store_api.process_list(store_api.toplist(user_home)) - except Http404: + toplist = Store(self.request.user).toplist() + except Exception: + logger.exception("Unable to get tolist for %s", + unicode(self.request.user)) toplist = [] cache.set(cache_key, toplist, 300) @@ -3092,14 +3094,12 @@ class StoreList(LoginRequiredMixin, TemplateView): directory = self.request.GET.get("directory", "/") directory = "/" if not len(directory) else directory - user_home = "u-%d" % self.request.user.pk - content = store_api.listfolder(user_home, directory) - context['root'] = store_api.process_list(content) + context['root'] = Store(self.request.user).list(directory) context['up_url'] = self.create_up_directory(directory) context['current'] = directory context['next_url'] = "%s%s?directory=%s" % ( - settings.DJANGO_URL[:-1], reverse("dashboard.views.store-list"), - directory) + settings.DJANGO_URL.rstrip("/"), + reverse("dashboard.views.store-list"), directory) return context def get(self, *args, **kwargs): @@ -3113,28 +3113,31 @@ class StoreList(LoginRequiredMixin, TemplateView): return super(StoreList, self).get(*args, **kwargs) def create_up_directory(self, directory): - cut = -2 if directory.endswith("/") else -1 - return "/".join(directory.split("/")[:cut]) + "/" + return normpath(join('/', directory, '..')) @require_GET @login_required def store_download(request): - user_home = "u-%d" % request.user.pk path = request.GET.get("path") - url = store_api.requestdownload(user_home, path) + url = Store(request.user).request_download(path) return redirect(url) @require_GET @login_required def store_upload(request): - user_home = "u-%d" % request.user.pk directory = request.GET.get("directory", "/") - action = store_api.requestupload(user_home, directory) + try: + action = Store(request.user).request_upload(directory) + except Exception: + logger.exception("Unable to upload") + messages.error(request, _("Unable to upload file.")) + return redirect("/") + next_url = "%s%s?directory=%s" % ( - settings.DJANGO_URL[:-1], reverse("dashboard.views.store-list"), - directory) + settings.DJANGO_URL.rstrip("/"), + reverse("dashboard.views.store-list"), directory) return render(request, "dashboard/store/upload.html", {'directory': directory, 'action': action, @@ -3144,9 +3147,13 @@ def store_upload(request): @require_GET @login_required def store_get_upload_url(request): - user_home = "u-%d" % request.user.pk current_dir = request.GET.get("current_dir") - url = store_api.requestupload(user_home, current_dir) + try: + url = Store(request.user).request_upload(current_dir) + except Exception: + logger.exception("Unable to upload") + messages.error(request, _("Unable to upload file.")) + return redirect("/") return HttpResponse( json.dumps({'url': url}), content_type="application/json") @@ -3171,30 +3178,31 @@ class StoreRemove(LoginRequiredMixin, TemplateView): return context def post(self, *args, **kwargs): - user_home = "u-%d" % self.request.user.pk path = self.request.POST.get("path") - store_api.requestremove(user_home, path) + try: + Store(self.request.user).remove(path) + except Exception: + logger.exception("Unable to remove %s", path) + messages.error(self.request, _("Unable to remove %s.") % path) - if path.endswith("/"): - return redirect("%s?directory=%s" % ( - reverse("dashboard.views.store-list"), - os.path.dirname(os.path.dirname(path)), - )) - else: - return redirect("%s?directory=%s" % ( - reverse("dashboard.views.store-list"), - os.path.dirname(path), - )) + return redirect("%s?directory=%s" % ( + reverse("dashboard.views.store-list"), + dirname(dirname(path)), + )) @require_POST @login_required def store_new_directory(request): - user_home = "u-%d" % request.user.pk path = request.POST.get("path") name = request.POST.get("name") - store_api.requestnewfolder(user_home, path + name) + try: + Store(request.user).new_folder(join(path, name)) + except Exception: + logger.exception("Unable to create folder %s in %s for %s", + name, path, unicode(request.user)) + messages.error(request, _("Unable to create folder.")) return redirect("%s?directory=%s" % ( reverse("dashboard.views.store-list"), path)) @@ -3202,12 +3210,12 @@ def store_new_directory(request): @require_POST @login_required def store_refresh_toplist(request): - user_home = "u-%d" % request.user.pk - cache_key = "toplist-%s" % user_home + cache_key = "toplist-%d" % request.user.pk cache = get_cache("default") try: - toplist = store_api.process_list(store_api.toplist(user_home)) - except Http404: + toplist = Store(request.user).toplist() + except Exception: + logger.exception("Can't get toplist of %s", unicode(request.user)) toplist = [] cache.set(cache_key, toplist, 300)