views.py 9.89 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

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
46
from synnefo.lib.astakos import get_user
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
47
48
from synnefo.db.models import VirtualMachine, NetworkInterface, Network
from synnefo.lib import astakos
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


61

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

77

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
78
79
80
81
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'])
82
83
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)

84
85
86
87
88
# guess cyclades setting too
USER_CATALOG_URL = getattr(settings, 'CYCLADES_USER_CATALOG_URL', None)
USER_CATALOG_URL = getattr(settings, 'HELPDESK_USER_CATALOG_URL',
                           USER_CATALOG_URL)

89
90
91
92
93
94
95
96
97
98

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

        raise PermissionDenied

    return wrapper
105

106

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

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

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

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

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

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

    return wrapper


@helpdesk_user_required
150
151
152
153
154
155
156
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
157
        return redirect('synnefo.helpdesk.views.account',
158
                        search_query=account)
159
160
161
162
163

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


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

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

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

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

    if is_uuid:
203
        account = search_query
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
204
205
        account_name = astakos.get_displayname(auth_token, account,
                                               USER_CATALOG_URL)
206
207
208

    if account_exists and not is_uuid:
        account_name = search_query
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
209
210
211
        account = astakos.get_user_uuid(auth_token, account_name,
                                        USER_CATALOG_URL)

212
213
214
    if not account:
        account_exists = False

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
    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
233

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

249
    return direct_to_template(request, "helpdesk/account.html",
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
250
                              extra_context=user_context)
251

Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
252

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


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


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