views.py 9.71 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
from snf_django.lib.astakos import get_user
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
47
from synnefo.db.models import VirtualMachine, NetworkInterface, Network
48
from astakosclient import AstakosClient
49

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

54
55
logger = logging.getLogger(__name__)

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


Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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

76

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
77
78
79
80
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'])
81
82
83
84
85
86
87
88
89
90
91
92
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)
93
        if token and token == request.user.get('auth_token', None):
94
95
96
97
98
            return func(request, *args, **kwargs)

        raise PermissionDenied

    return wrapper
99

100

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

111
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
112
113
        get_user(request, settings.ASTAKOS_URL,
                 fallback_token=token, logger=logger)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
114
        if hasattr(request, 'user') and request.user:
115
            groups = request.user.get('groups', [])
116
117

            if not groups:
118
119
                logger.error("Failed to access helpdesk view %r",
                             request.user_uniq)
120
121
                raise PermissionDenied

122
            has_perm = False
123
            for g in groups:
124
125
126
127
                if g in permitted_groups:
                    has_perm = True

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

137
138
        logging.info("User %s accessed helpdesk view (%s)", request.user_uniq,
                     request.path)
139
140
141
142
143
144
        return func(request, *args, **kwargs)

    return wrapper


@helpdesk_user_required
145
146
147
148
149
150
151
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
152
        return redirect('synnefo.helpdesk.views.account',
153
                        search_query=account)
154
155
156
157
158

    # show index template
    return direct_to_template(request, "helpdesk/index.html")


159
@helpdesk_user_required
160
def account(request, search_query):
161
    """
162
    Account details view.
163
    """
164

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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
168
    account_exists = True
169
170
    vms = []
    networks = []
171
172
173
174
    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
175
    auth_token = request.user.get('auth_token')
176
177
178

    if is_ip:
        try:
179
180
            nic = NetworkInterface.objects.get(ipv4=search_query)
            search_query = nic.machine.userid
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
181
            is_uuid = True
182
183
        except NetworkInterface.DoesNotExist:
            account_exists = False
184
185
186
187
188
189
190
191
192
193
194
195
            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
196

197
198
199
    astakos = AstakosClient(settings.ASTAKOS_URL, retry=2,
                            use_pool=True, logger=logger)

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
200
    if is_uuid:
201
        account = search_query
202
        account_name = astakos.get_username(auth_token, account)
203
204
205

    if account_exists and not is_uuid:
        account_name = search_query
206
        account = astakos.get_uuid(auth_token, account_name)
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
207

208
209
210
    if not account:
        account_exists = False

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
    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
229

230
    user_context = {
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
231
        'account_exists': account_exists,
232
        'is_ip': is_ip,
233
234
        'is_vm': is_vm,
        'is_uuid': is_uuid,
235
        'account': account,
236
        'search_query': search_query,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
237
        'vms': vms,
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
238
239
        'show_deleted': show_deleted,
        'account_name': account_name,
240
        'token': request.user['auth_token'],
241
        'networks': networks,
Olga Brani's avatar
Olga Brani committed
242
        'UI_MEDIA_URL': settings.UI_MEDIA_URL
243
    }
244

245
    return direct_to_template(request, "helpdesk/account.html",
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
246
                              extra_context=user_context)
247

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
248

249
250
@helpdesk_user_required
@token_check
251
def vm_suspend(request, vm_id):
252
253
254
    vm = VirtualMachine.objects.get(pk=vm_id)
    vm.suspended = True
    vm.save()
255
    logging.info("VM %s suspended by %s", vm_id, request.user_uniq)
256
257
258
259
260
261
    account = vm.userid
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))


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


@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,)))