Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / helpdesk / views.py @ 19b2c29d

History | View | Annotate | Download (11.7 kB)

1
# Copyright 2012, 2013 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, Network, IPAddressLog
50

    
51
# server actions specific imports
52
from synnefo.logic import servers as servers_backend
53
from synnefo.ui.views import UI_MEDIA_URL
54

    
55
logger = logging.getLogger(__name__)
56

    
57
HELPDESK_MEDIA_URL = getattr(settings, 'HELPDESK_MEDIA_URL',
58
                             settings.MEDIA_URL + 'helpdesk/')
59

    
60
IP_SEARCH_REGEX = re.compile('([0-9]+)(?:\.[0-9]+){3}')
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]+)')
63

    
64

    
65
def get_token_from_cookie(request, cookiename):
66
    """
67
    Extract token from the cookie name provided. Cookie should be in the same
68
    form as astakos service sets its cookie contents::
69

70
        <user_uniq>|<user_token>
71
    """
72
    try:
73
        cookie_content = unquote(request.COOKIES.get(cookiename, None))
74
        return cookie_content.split("|")[1]
75
    except AttributeError:
76
        pass
77

    
78
    return None
79

    
80

    
81
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME',
82
                           getattr(settings, 'UI_AUTH_COOKIE_NAME',
83
                                   '_pithos2_a'))
84
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS', ['helpdesk'])
85
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)
86

    
87

    
88
def token_check(func):
89
    """
90
    Mimic csrf security check using user auth token.
91
    """
92
    def wrapper(request, *args, **kwargs):
93
        if not hasattr(request, 'user'):
94
            raise PermissionDenied
95

    
96
        token = request.POST.get('token', None)
97
        if token:
98
            try:
99
                req_token = request.user["access"]["token"]["id"]
100
                if token == req_token:
101
                    return func(request, *args, **kwargs)
102
            except KeyError:
103
                pass
104

    
105
        raise PermissionDenied
106

    
107
    return wrapper
108

    
109

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

    
120
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
121
        astakos.get_user(request, settings.ASTAKOS_AUTH_URL,
122
                         fallback_token=token, logger=logger)
123
        if hasattr(request, 'user') and request.user:
124
            groups = request.user['access']['user']['roles']
125
            groups = [g["name"] for g in groups]
126

    
127
            if not groups:
128
                logger.info("Failed to access helpdesk view. User: %r",
129
                            request.user_uniq)
130
                raise PermissionDenied
131

    
132
            has_perm = False
133
            for g in groups:
134
                if g in permitted_groups:
135
                    has_perm = True
136

    
137
            if not has_perm:
138
                logger.info("Failed to access helpdesk view %r. No valid "
139
                            "helpdesk group (%r) matches user groups (%r)",
140
                            request.user_uniq, permitted_groups, groups)
141
                raise PermissionDenied
142
        else:
143
            logger.info("Failed to access helpdesk view %r. No authenticated "
144
                        "user found.", request.user_uniq)
145
            raise PermissionDenied
146

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

    
151
    return wrapper
152

    
153

    
154
@helpdesk_user_required
155
def index(request):
156
    """
157
    Helpdesk index view.
158
    """
159
    # if form submitted redirect to details
160
    account = request.GET.get('account', None)
161
    if account:
162
        return redirect('synnefo.helpdesk.views.account',
163
                        search_query=account)
164

    
165
    # show index template
166
    return direct_to_template(request, "helpdesk/index.html",
167
                              extra_context={'HELPDESK_MEDIA_URL':
168
                                             HELPDESK_MEDIA_URL})
169

    
170

    
171
@helpdesk_user_required
172
def account(request, search_query):
173
    """
174
    Account details view.
175
    """
176

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

    
180
    account_exists = True
181
    # flag to indicate successfull astakos calls
182
    account_resolved = False
183
    vms = []
184
    networks = []
185
    is_ip = IP_SEARCH_REGEX.match(search_query)
186
    is_uuid = UUID_SEARCH_REGEX.match(search_query)
187
    is_vm = VM_SEARCH_REGEX.match(search_query)
188
    account_name = search_query
189
    auth_token = request.user['access']['token']['id']
190

    
191
    if is_ip:
192
        # Search the IPAddressLog for the full use history of this IP
193
        return search_by_ip(request, search_query)
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(
207
        auth_token, settings.ASTAKOS_AUTH_URL,
208
        retry=2, use_pool=True, logger=logger)
209

    
210
    account = None
211
    if is_uuid:
212
        account = search_query
213
        try:
214
            account_name = astakos_client.get_username(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(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['access']['token']['id'],
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
def search_by_ip(request, search_query):
271
    """Search IP history for all uses of an IP address."""
272
    auth_token = request.user['access']['token']['id']
273
    astakos_client = astakosclient.AstakosClient(auth_token,
274
                                                 settings.ASTAKOS_AUTH_URL,
275
                                                 retry=2, use_pool=True,
276
                                                 logger=logger)
277

    
278
    ips = IPAddressLog.objects.filter(address=search_query)\
279
                              .order_by("allocated_at")
280

    
281
    for ip in ips:
282
        # Annotate IPs with the VM, Network and account attributes
283
        ip.vm = VirtualMachine.objects.get(id=ip.server_id)
284
        ip.network = Network.objects.get(id=ip.network_id)
285
        userid = ip.vm.userid
286

    
287
        try:
288
            ip.account = astakos_client.get_username(userid)
289
        except:
290
            ip.account = userid
291
            logger.info("Failed to resolve '%s' into account" % userid)
292

    
293
    user_context = {
294
        'ip_exists': bool(ips),
295
        'ips': ips,
296
        'search_query': search_query,
297
        'token': auth_token,
298
        'HELPDESK_MEDIA_URL': HELPDESK_MEDIA_URL,
299
        'UI_MEDIA_URL': UI_MEDIA_URL
300
    }
301

    
302
    return direct_to_template(request, "helpdesk/ip.html",
303
                              extra_context=user_context)
304

    
305

    
306
@helpdesk_user_required
307
@token_check
308
def vm_suspend(request, vm_id):
309
    vm = VirtualMachine.objects.get(pk=vm_id)
310
    vm.suspended = True
311
    vm.save()
312
    logging.info("VM %s suspended by %s", vm_id, request.user_uniq)
313
    account = vm.userid
314
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
315

    
316

    
317
@helpdesk_user_required
318
@token_check
319
def vm_suspend_release(request, vm_id):
320
    vm = VirtualMachine.objects.get(pk=vm_id)
321
    vm.suspended = False
322
    vm.save()
323
    logging.info("VM %s unsuspended by %s", vm_id, request.user_uniq)
324
    account = vm.userid
325
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
326

    
327

    
328
@helpdesk_user_required
329
@token_check
330
def vm_shutdown(request, vm_id):
331
    logging.info("VM %s shutdown by %s", vm_id, request.user_uniq)
332
    vm = VirtualMachine.objects.get(pk=vm_id)
333
    jobId = servers_backend.stop(vm)
334
    account = vm.userid
335
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
336

    
337

    
338
@helpdesk_user_required
339
@token_check
340
def vm_start(request, vm_id):
341
    logging.info("VM %s start by %s", vm_id, request.user_uniq)
342
    vm = VirtualMachine.objects.get(pk=vm_id)
343
    jobId = servers_backend.start(vm)
344
    account = vm.userid
345
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))