Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.2 kB)

1
# Copyright 2012 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 servers
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 and token == request.user.get('auth_token', None):
99
            return func(request, *args, **kwargs)
100

    
101
        raise PermissionDenied
102

    
103
    return wrapper
104

    
105

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

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

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

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

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

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

    
146
    return wrapper
147

    
148

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

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

    
165

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

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

    
175
    account_exists = True
176
    vms = []
177
    networks = []
178
    is_ip = IP_SEARCH_REGEX.match(search_query)
179
    is_uuid = UUID_SEARCH_REGEX.match(search_query)
180
    is_vm = VM_SEARCH_REGEX.match(search_query)
181
    account_name = search_query
182
    auth_token = request.user.get('auth_token')
183

    
184
    if is_ip:
185
        try:
186
            nic = NetworkInterface.objects.get(ipv4=search_query)
187
            search_query = nic.machine.userid
188
            is_uuid = True
189
        except NetworkInterface.DoesNotExist:
190
            account_exists = False
191
            account = None
192

    
193
    if is_vm:
194
        vmid = is_vm.groupdict().get('vmid')
195
        try:
196
            vm = VirtualMachine.objects.get(pk=int(vmid))
197
            search_query = vm.userid
198
            is_uuid = True
199
        except VirtualMachine.DoesNotExist:
200
            account_exists = False
201
            account = None
202
            search_query = vmid
203

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

    
208
    if is_uuid:
209
        account = search_query
210
        account_name = astakos_client.get_username(auth_token, account)
211

    
212
    if account_exists and not is_uuid:
213
        account_name = search_query
214
        account = astakos_client.get_uuid(auth_token, account_name)
215

    
216
    if not account:
217
        account_exists = False
218

    
219
    filter_extra = {}
220
    if not show_deleted:
221
        filter_extra['deleted'] = False
222

    
223
    # all user vms
224
    vms = VirtualMachine.objects.filter(userid=account,
225
                                        **filter_extra).order_by('deleted')
226
    # return all user private and public networks
227
    public_networks = Network.objects.filter(public=True,
228
                                             nics__machine__userid=account,
229
                                             **filter_extra
230
                                             ).order_by('state').distinct()
231
    private_networks = Network.objects.filter(userid=account,
232
                                              **filter_extra).order_by('state')
233
    networks = list(public_networks) + list(private_networks)
234

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

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

    
254
    return direct_to_template(request, "helpdesk/account.html",
255
                              extra_context=user_context)
256

    
257

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

    
268

    
269
@helpdesk_user_required
270
@token_check
271
def vm_suspend_release(request, vm_id):
272
    vm = VirtualMachine.objects.get(pk=vm_id)
273
    vm.suspended = False
274
    vm.save()
275
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
276
    account = vm.userid
277
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
278

    
279

    
280
@helpdesk_user_required
281
@token_check
282
def vm_shutdown(request, vm_id):
283
    logging.info("VM %s shutdown by %s", vm_id, request.user_uniq)
284
    vm = VirtualMachine.objects.get(pk=vm_id)
285
    servers.start_action(vm, 'STOP')
286
    servers_backend.shutdown_instance(vm)
287
    account = vm.userid
288
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
289

    
290

    
291
@helpdesk_user_required
292
@token_check
293
def vm_start(request, vm_id):
294
    logging.info("VM %s start by %s", vm_id, request.user_uniq)
295
    vm = VirtualMachine.objects.get(pk=vm_id)
296
    servers.start_action(vm, 'START')
297
    servers_backend.startup_instance(vm)
298
    account = vm.userid
299
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))