Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / helpdesk / views.py @ b4b82ec4

History | View | Annotate | Download (10.7 kB)

1
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import re
35
import logging
36

    
37
from django.shortcuts import redirect
38
from django.views.generic.simple import direct_to_template
39
from django.conf import settings
40
from django.core.exceptions import PermissionDenied
41
from django.http import Http404, HttpResponseRedirect
42
from django.core.urlresolvers import reverse
43

    
44
from urllib import unquote
45

    
46
import astakosclient
47
from snf_django.lib import astakos
48

    
49
from synnefo.db.models import VirtualMachine, NetworkInterface, Network
50

    
51
# server actions specific imports
52
from synnefo.api import util
53
from synnefo.logic import backend as servers_backend
54
from synnefo.ui.views import UI_MEDIA_URL
55

    
56
logger = logging.getLogger(__name__)
57

    
58
HELPDESK_MEDIA_URL = getattr(settings, 'HELPDESK_MEDIA_URL',
59
                             settings.MEDIA_URL + 'helpdesk/')
60

    
61
IP_SEARCH_REGEX = re.compile('([0-9]+)(?:\.[0-9]+){3}')
62
UUID_SEARCH_REGEX = re.compile('([0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12})')
63
VM_SEARCH_REGEX = re.compile('vm(-){0,}(?P<vmid>[0-9]+)')
64

    
65

    
66
def get_token_from_cookie(request, cookiename):
67
    """
68
    Extract token from the cookie name provided. Cookie should be in the same
69
    form as astakos service sets its cookie contents::
70

71
        <user_uniq>|<user_token>
72
    """
73
    try:
74
        cookie_content = unquote(request.COOKIES.get(cookiename, None))
75
        return cookie_content.split("|")[1]
76
    except AttributeError:
77
        pass
78

    
79
    return None
80

    
81

    
82
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME',
83
                           getattr(settings, 'UI_AUTH_COOKIE_NAME',
84
                                   '_pithos2_a'))
85
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS', ['helpdesk'])
86
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)
87

    
88

    
89
def token_check(func):
90
    """
91
    Mimic csrf security check using user auth token.
92
    """
93
    def wrapper(request, *args, **kwargs):
94
        if not hasattr(request, 'user'):
95
            raise PermissionDenied
96

    
97
        token = request.POST.get('token', None)
98
        if token:
99
            try:
100
                req_token = request.user["access"]["token"]["id"]
101
                if token == req_token:
102
                    return func(request, *args, **kwargs)
103
            except KeyError:
104
                pass
105

    
106
        raise PermissionDenied
107

    
108
    return wrapper
109

    
110

    
111
def helpdesk_user_required(func, permitted_groups=PERMITTED_GROUPS):
112
    """
113
    Django view wrapper that checks if identified request user has helpdesk
114
    permissions (exists in helpdesk group)
115
    """
116
    def wrapper(request, *args, **kwargs):
117
        HELPDESK_ENABLED = getattr(settings, 'HELPDESK_ENABLED', True)
118
        if not HELPDESK_ENABLED:
119
            raise Http404
120

    
121
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
122
        astakos.get_user(request, settings.ASTAKOS_AUTH_URL,
123
                         fallback_token=token, logger=logger)
124
        if hasattr(request, 'user') and request.user:
125
            groups = request.user['access']['user']['roles']
126
            groups = [g["name"] for g in groups]
127

    
128
            if not groups:
129
                logger.error("Failed to access helpdesk view. User: %r",
130
                             request.user_uniq)
131
                raise PermissionDenied
132

    
133
            has_perm = False
134
            for g in groups:
135
                if g in permitted_groups:
136
                    has_perm = True
137

    
138
            if not has_perm:
139
                logger.error("Failed to access helpdesk view %r. No valid "
140
                             "helpdesk group (%r) matches user groups (%r)",
141
                             request.user_uniq, permitted_groups, groups)
142
                raise PermissionDenied
143
        else:
144
            logger.error("Failed to access helpdesk view %r. No authenticated "
145
                         "user found.", request.user_uniq)
146
            raise PermissionDenied
147

    
148
        logging.info("User %s accessed helpdesk view (%s)", request.user_uniq,
149
                     request.path)
150
        return func(request, *args, **kwargs)
151

    
152
    return wrapper
153

    
154

    
155
@helpdesk_user_required
156
def index(request):
157
    """
158
    Helpdesk index view.
159
    """
160
    # if form submitted redirect to details
161
    account = request.GET.get('account', None)
162
    if account:
163
        return redirect('synnefo.helpdesk.views.account',
164
                        search_query=account)
165

    
166
    # show index template
167
    return direct_to_template(request, "helpdesk/index.html",
168
                              extra_context={'HELPDESK_MEDIA_URL':
169
                                             HELPDESK_MEDIA_URL})
170

    
171

    
172
@helpdesk_user_required
173
def account(request, search_query):
174
    """
175
    Account details view.
176
    """
177

    
178
    logging.info("Helpdesk search by %s: %s", request.user_uniq, search_query)
179
    show_deleted = bool(int(request.GET.get('deleted', SHOW_DELETED_VMS)))
180

    
181
    account_exists = True
182
    # flag to indicate successfull astakos calls
183
    account_resolved = False
184
    vms = []
185
    networks = []
186
    is_ip = IP_SEARCH_REGEX.match(search_query)
187
    is_uuid = UUID_SEARCH_REGEX.match(search_query)
188
    is_vm = VM_SEARCH_REGEX.match(search_query)
189
    account_name = search_query
190
    auth_token = request.user['access']['token']['id']
191

    
192
    if is_ip:
193
        try:
194
            nic = NetworkInterface.objects.filter(ipv4=search_query).exclude(
195
                machine__deleted=True).get()
196
            search_query = nic.machine.userid
197
            is_uuid = True
198
        except NetworkInterface.DoesNotExist:
199
            account_exists = False
200
            account = None
201

    
202
    if is_vm:
203
        vmid = is_vm.groupdict().get('vmid')
204
        try:
205
            vm = VirtualMachine.objects.get(pk=int(vmid))
206
            search_query = vm.userid
207
            is_uuid = True
208
        except VirtualMachine.DoesNotExist:
209
            account_exists = False
210
            account = None
211
            search_query = vmid
212

    
213
    astakos_client = astakosclient.AstakosClient(
214
        auth_token, settings.ASTAKOS_AUTH_URL,
215
        retry=2, use_pool=True, logger=logger)
216

    
217
    account = None
218
    if is_uuid:
219
        account = search_query
220
        try:
221
            account_name = astakos_client.get_username(account)
222
        except:
223
            logger.info("Failed to resolve '%s' into account" % account)
224

    
225
    if account_exists and not is_uuid:
226
        account_name = search_query
227
        try:
228
            account = astakos_client.get_uuid(account_name)
229
        except:
230
            logger.info("Failed to resolve '%s' into account" % account_name)
231

    
232
    if not account:
233
        account_exists = False
234
    else:
235
        account_resolved = True
236

    
237
    filter_extra = {}
238
    if not show_deleted:
239
        filter_extra['deleted'] = False
240

    
241
    # all user vms
242
    vms = VirtualMachine.objects.filter(userid=account,
243
                                        **filter_extra).order_by('deleted')
244
    # return all user private and public networks
245
    public_networks = Network.objects.filter(public=True,
246
                                             nics__machine__userid=account,
247
                                             **filter_extra
248
                                             ).order_by('state').distinct()
249
    private_networks = Network.objects.filter(userid=account,
250
                                              **filter_extra).order_by('state')
251
    networks = list(public_networks) + list(private_networks)
252

    
253
    if vms.count() == 0 and private_networks.count() == 0 and not \
254
            account_resolved:
255
        account_exists = False
256

    
257
    user_context = {
258
        'account_exists': account_exists,
259
        'is_ip': is_ip,
260
        'is_vm': is_vm,
261
        'is_uuid': is_uuid,
262
        'account': account,
263
        'search_query': search_query,
264
        'vms': vms,
265
        'show_deleted': show_deleted,
266
        'account_name': account_name,
267
        'token': request.user['access']['token']['id'],
268
        'networks': networks,
269
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL,
270
        'UI_MEDIA_URL': UI_MEDIA_URL
271
    }
272

    
273
    return direct_to_template(request, "helpdesk/account.html",
274
                              extra_context=user_context)
275

    
276

    
277
@helpdesk_user_required
278
@token_check
279
def vm_suspend(request, vm_id):
280
    vm = VirtualMachine.objects.get(pk=vm_id)
281
    vm.suspended = True
282
    vm.save()
283
    logging.info("VM %s suspended by %s", vm_id, request.user_uniq)
284
    account = vm.userid
285
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
286

    
287

    
288
@helpdesk_user_required
289
@token_check
290
def vm_suspend_release(request, vm_id):
291
    vm = VirtualMachine.objects.get(pk=vm_id)
292
    vm.suspended = False
293
    vm.save()
294
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
295
    account = vm.userid
296
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
297

    
298

    
299
@helpdesk_user_required
300
@token_check
301
def vm_shutdown(request, vm_id):
302
    logging.info("VM %s shutdown by %s", vm_id, request.user_uniq)
303
    vm = VirtualMachine.objects.get(pk=vm_id)
304
    jobId = servers_backend.shutdown_instance(vm)
305
    util.start_action(vm, 'STOP', jobId)
306
    account = vm.userid
307
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
308

    
309

    
310
@helpdesk_user_required
311
@token_check
312
def vm_start(request, vm_id):
313
    logging.info("VM %s start by %s", vm_id, request.user_uniq)
314
    vm = VirtualMachine.objects.get(pk=vm_id)
315
    jobId = servers_backend.startup_instance(vm)
316
    util.start_action(vm, 'START', jobId)
317
    account = vm.userid
318
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))