views.py 10.1 KB
Newer Older
1
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
# Copyright 2012 GRNET S.A. All rights reserved.
#
# 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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
49
from synnefo.db.models import VirtualMachine, NetworkInterface, Network
50

51
52
53
54
# server actions specific imports
from synnefo.api import servers
from synnefo.logic import backend as servers_backend

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
        if token and token == request.user.get('auth_token', None):
98
99
100
101
102
            return func(request, *args, **kwargs)

        raise PermissionDenied

    return wrapper
103

104

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

115
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
116
        astakos.get_user(request, settings.ASTAKOS_BASE_URL,
117
                         fallback_token=token, logger=logger)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
118
        if hasattr(request, 'user') and request.user:
119
            groups = request.user.get('groups', [])
120
121

            if not groups:
122
                logger.error("Failed to access helpdesk view. User: %r",
123
                             request.user_uniq)
124
125
                raise PermissionDenied

126
            has_perm = False
127
            for g in groups:
128
129
130
131
                if g in permitted_groups:
                    has_perm = True

            if not has_perm:
132
                logger.error("Failed to access helpdesk view %r. No valid "
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
133
134
                             "helpdesk group (%r) matches user groups (%r)",
                             request.user_uniq, permitted_groups, groups)
135
                raise PermissionDenied
136
        else:
137
            logger.error("Failed to access helpdesk view %r. No authenticated "
138
                         "user found.", request.user_uniq)
139
140
            raise PermissionDenied

141
142
        logging.info("User %s accessed helpdesk view (%s)", request.user_uniq,
                     request.path)
143
144
145
146
147
148
        return func(request, *args, **kwargs)

    return wrapper


@helpdesk_user_required
149
150
151
152
153
154
155
def index(request):
    """
    Helpdesk index view.
    """
    # if form submitted redirect to details
    account = request.GET.get('account', None)
    if account:
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
156
        return redirect('synnefo.helpdesk.views.account',
157
                        search_query=account)
158
159

    # show index template
160
161
162
    return direct_to_template(request, "helpdesk/index.html",
                              extra_context={'HELPDESK_MEDIA_URL':
                                             HELPDESK_MEDIA_URL})
163
164


165
@helpdesk_user_required
166
def account(request, search_query):
167
    """
168
    Account details view.
169
    """
170

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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
174
    account_exists = True
175
176
    vms = []
    networks = []
177
178
179
180
    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
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
181
    auth_token = request.user.get('auth_token')
182
183
184

    if is_ip:
        try:
185
186
            nic = NetworkInterface.objects.get(ipv4=search_query)
            search_query = nic.machine.userid
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
187
            is_uuid = True
188
189
        except NetworkInterface.DoesNotExist:
            account_exists = False
190
191
192
193
194
195
196
197
198
199
200
201
            account = None

    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
202

203
204
205
    astakos_client = astakosclient.AstakosClient(settings.ASTAKOS_BASE_URL,
                                                 retry=2, use_pool=True,
                                                 logger=logger)
206

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
207
    if is_uuid:
208
        account = search_query
209
        account_name = astakos_client.get_username(auth_token, account)
210
211
212

    if account_exists and not is_uuid:
        account_name = search_query
213
        account = astakos_client.get_uuid(auth_token, account_name)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
214

215
216
217
    if not account:
        account_exists = False

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    filter_extra = {}
    if not show_deleted:
        filter_extra['deleted'] = False

    # 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)

    if vms.count() == 0 and private_networks.count() == 0:
        account_exists = False
236

237
    user_context = {
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
238
        'account_exists': account_exists,
239
        'is_ip': is_ip,
240
241
        'is_vm': is_vm,
        'is_uuid': is_uuid,
242
        'account': account,
243
        'search_query': search_query,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
244
        'vms': vms,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
245
246
        'show_deleted': show_deleted,
        'account_name': account_name,
247
        'token': request.user['auth_token'],
248
        'networks': networks,
249
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL
250
    }
251

252
    return direct_to_template(request, "helpdesk/account.html",
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
253
                              extra_context=user_context)
254

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
255

256
257
@helpdesk_user_required
@token_check
258
def vm_suspend(request, vm_id):
259
260
261
    vm = VirtualMachine.objects.get(pk=vm_id)
    vm.suspended = True
    vm.save()
262
    logging.info("VM %s suspended by %s", vm_id, request.user_uniq)
263
264
265
266
267
268
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))


@helpdesk_user_required
@token_check
269
def vm_suspend_release(request, vm_id):
270
271
272
    vm = VirtualMachine.objects.get(pk=vm_id)
    vm.suspended = False
    vm.save()
273
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
274
275
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297


@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)
    servers.start_action(vm, 'STOP')
    servers_backend.shutdown_instance(vm)
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))


@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)
    servers.start_action(vm, 'START')
    servers_backend.startup_instance(vm)
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))