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

34
import re
35
import logging
36

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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
44
from urllib import unquote
45

46
47
48
import astakosclient
from snf_django.lib import astakos

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

51
# server actions specific imports
52
from synnefo.logic import servers as servers_backend
53
from synnefo.ui.views import UI_MEDIA_URL
54

55
56
logger = logging.getLogger(__name__)

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

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


Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def get_token_from_cookie(request, cookiename):
    """
    Extract token from the cookie name provided. Cookie should be in the same
    form as astakos service sets its cookie contents::

        <user_uniq>|<user_token>
    """
    try:
        cookie_content = unquote(request.COOKIES.get(cookiename, None))
        return cookie_content.split("|")[1]
    except AttributeError:
        pass

    return None

80

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
81
82
83
84
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME',
                           getattr(settings, 'UI_AUTH_COOKIE_NAME',
                                   '_pithos2_a'))
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS', ['helpdesk'])
85
86
87
88
89
90
91
92
93
94
95
96
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)


def token_check(func):
    """
    Mimic csrf security check using user auth token.
    """
    def wrapper(request, *args, **kwargs):
        if not hasattr(request, 'user'):
            raise PermissionDenied

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

        raise PermissionDenied

    return wrapper
108

109

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

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

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

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

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

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

    return wrapper


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

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


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

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

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

    if is_ip:
193
194
        # Search the IPAddressLog for the full use history of this IP
        return search_by_ip(request, search_query)
195
196
197
198
199
200
201
202
203
204
205

    if is_vm:
        vmid = is_vm.groupdict().get('vmid')
        try:
            vm = VirtualMachine.objects.get(pk=int(vmid))
            search_query = vm.userid
            is_uuid = True
        except VirtualMachine.DoesNotExist:
            account_exists = False
            account = None
            search_query = vmid
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
206

207
208
209
    astakos_client = astakosclient.AstakosClient(
        auth_token, settings.ASTAKOS_AUTH_URL,
        retry=2, use_pool=True, logger=logger)
210

211
    account = None
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
212
    if is_uuid:
213
        account = search_query
214
        try:
215
            account_name = astakos_client.get_username(account)
216
217
        except:
            logger.info("Failed to resolve '%s' into account" % account)
218
219
220

    if account_exists and not is_uuid:
        account_name = search_query
221
        try:
222
            account = astakos_client.get_uuid(account_name)
223
224
        except:
            logger.info("Failed to resolve '%s' into account" % account_name)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
225

226
227
    if not account:
        account_exists = False
228
229
    else:
        account_resolved = True
230

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
231
232
    filter_extra = {}
    if not show_deleted:
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
233
        filter_extra['deleted'] = False
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
234
235
236
237
238
239
240
241
242
243
244
245
246

    # all user vms
    vms = VirtualMachine.objects.filter(userid=account,
                                        **filter_extra).order_by('deleted')
    # return all user private and public networks
    public_networks = Network.objects.filter(public=True,
                                             nics__machine__userid=account,
                                             **filter_extra
                                             ).order_by('state').distinct()
    private_networks = Network.objects.filter(userid=account,
                                              **filter_extra).order_by('state')
    networks = list(public_networks) + list(private_networks)

247
248
    if vms.count() == 0 and private_networks.count() == 0 and not \
            account_resolved:
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
249
        account_exists = False
250

251
    user_context = {
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
252
        'account_exists': account_exists,
253
        'error': error,
254
        'is_ip': is_ip,
255
256
        'is_vm': is_vm,
        'is_uuid': is_uuid,
257
        'account': account,
258
        'search_query': search_query,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
259
        'vms': vms,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
260
261
        'show_deleted': show_deleted,
        'account_name': account_name,
262
        'token': request.user['access']['token']['id'],
263
        'networks': networks,
264
265
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL,
        'UI_MEDIA_URL': UI_MEDIA_URL
266
    }
267

268
    return direct_to_template(request, "helpdesk/account.html",
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
269
                              extra_context=user_context)
270

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
271

272
273
274
275
276
277
278
279
def search_by_ip(request, search_query):
    """Search IP history for all uses of an IP address."""
    auth_token = request.user['access']['token']['id']
    astakos_client = astakosclient.AstakosClient(auth_token,
                                                 settings.ASTAKOS_AUTH_URL,
                                                 retry=2, use_pool=True,
                                                 logger=logger)

280
281
    ips = IPAddressLog.objects.filter(address=search_query)\
                              .order_by("allocated_at")
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306

    for ip in ips:
        # Annotate IPs with the VM, Network and account attributes
        ip.vm = VirtualMachine.objects.get(id=ip.server_id)
        ip.network = Network.objects.get(id=ip.network_id)
        userid = ip.vm.userid

        try:
            ip.account = astakos_client.get_username(userid)
        except:
            ip.account = userid
            logger.info("Failed to resolve '%s' into account" % userid)

    user_context = {
        'ip_exists': bool(ips),
        'ips': ips,
        'search_query': search_query,
        'token': auth_token,
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL,
        'UI_MEDIA_URL': UI_MEDIA_URL
    }

    return direct_to_template(request, "helpdesk/ip.html",
                              extra_context=user_context)

307

308
309
@helpdesk_user_required
@token_check
310
def vm_suspend(request, vm_id):
311
312
313
    vm = VirtualMachine.objects.get(pk=vm_id)
    vm.suspended = True
    vm.save()
314
    logging.info("VM %s suspended by %s", vm_id, request.user_uniq)
315
316
317
318
319
320
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))


@helpdesk_user_required
@token_check
321
def vm_suspend_release(request, vm_id):
322
323
324
    vm = VirtualMachine.objects.get(pk=vm_id)
    vm.suspended = False
    vm.save()
325
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
326
327
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
328
329
330
331
332
333
334
335


@helpdesk_user_required
@token_check
def vm_shutdown(request, vm_id):
    logging.info("VM %s shutdown by %s", vm_id, request.user_uniq)
    vm = VirtualMachine.objects.get(pk=vm_id)
    account = vm.userid
336
337
338
339
340
341
342
343
344
345
    error = None
    try:
        jobId = servers_backend.stop(vm)
    except Exception, e:
        error = e.message

    redirect = reverse('helpdesk-details', args=(account,))
    if error:
        redirect = "%s?error=%s" % (redirect, error)
    return HttpResponseRedirect(redirect)
346
347
348
349
350
351
352
353


@helpdesk_user_required
@token_check
def vm_start(request, vm_id):
    logging.info("VM %s start by %s", vm_id, request.user_uniq)
    vm = VirtualMachine.objects.get(pk=vm_id)
    account = vm.userid
354
355
356
357
358
359
360
361
362
363
    error = None
    try:
        jobId = servers_backend.start(vm)
    except Exception, e:
        error = e.message

    redirect = reverse('helpdesk-details', args=(account,))
    if error:
        redirect = "%s?error=%s" % (redirect, error)
    return HttpResponseRedirect(redirect)