Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / helpdesk / views.py @ 0c0498c8

History | View | Annotate | Download (10.6 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
    # flag to indicate successfull astakos calls
177
    account_resolved = False
178
    vms = []
179
    networks = []
180
    is_ip = IP_SEARCH_REGEX.match(search_query)
181
    is_uuid = UUID_SEARCH_REGEX.match(search_query)
182
    is_vm = VM_SEARCH_REGEX.match(search_query)
183
    account_name = search_query
184
    auth_token = request.user.get('auth_token')
185

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

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

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

    
210
    account = None
211
    if is_uuid:
212
        account = search_query
213
        try:
214
            account_name = astakos_client.get_username(auth_token, account)
215
        except:
216
            logger.info("Failed to resolve '%s' into account" % account)
217

    
218
    if account_exists and not is_uuid:
219
        account_name = search_query
220
        try:
221
            account = astakos_client.get_uuid(auth_token, account_name)
222
        except:
223
            logger.info("Failed to resolve '%s' into account" % account_name)
224

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

    
230
    filter_extra = {}
231
    if not show_deleted:
232
        filter_extra['deleted'] = False
233

    
234
    # all user vms
235
    vms = VirtualMachine.objects.filter(userid=account,
236
                                        **filter_extra).order_by('deleted')
237
    # return all user private and public networks
238
    public_networks = Network.objects.filter(public=True,
239
                                             nics__machine__userid=account,
240
                                             **filter_extra
241
                                             ).order_by('state').distinct()
242
    private_networks = Network.objects.filter(userid=account,
243
                                              **filter_extra).order_by('state')
244
    networks = list(public_networks) + list(private_networks)
245

    
246
    if vms.count() == 0 and private_networks.count() == 0 and not \
247
            account_resolved:
248
        account_exists = False
249

    
250
    user_context = {
251
        'account_exists': account_exists,
252
        'is_ip': is_ip,
253
        'is_vm': is_vm,
254
        'is_uuid': is_uuid,
255
        'account': account,
256
        'search_query': search_query,
257
        'vms': vms,
258
        'show_deleted': show_deleted,
259
        'account_name': account_name,
260
        'token': request.user['auth_token'],
261
        'networks': networks,
262
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL,
263
        'UI_MEDIA_URL': UI_MEDIA_URL
264
    }
265

    
266
    return direct_to_template(request, "helpdesk/account.html",
267
                              extra_context=user_context)
268

    
269

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

    
280

    
281
@helpdesk_user_required
282
@token_check
283
def vm_suspend_release(request, vm_id):
284
    vm = VirtualMachine.objects.get(pk=vm_id)
285
    vm.suspended = False
286
    vm.save()
287
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
288
    account = vm.userid
289
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
290

    
291

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

    
302

    
303
@helpdesk_user_required
304
@token_check
305
def vm_start(request, vm_id):
306
    logging.info("VM %s start by %s", vm_id, request.user_uniq)
307
    vm = VirtualMachine.objects.get(pk=vm_id)
308
    servers.start_action(vm, 'START')
309
    servers_backend.startup_instance(vm)
310
    account = vm.userid
311
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))