Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / helpdesk / views.py @ 7ad07359

History | View | Annotate | Download (10 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

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

    
100
        raise PermissionDenied
101

    
102
    return wrapper
103

    
104

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

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

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

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

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

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

    
145
    return wrapper
146

    
147

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

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

    
164

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

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

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

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

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

    
203
    astakos_client = astakosclient.AstakosClient(settings.ASTAKOS_URL, retry=2,
204
                                                 use_pool=True, logger=logger)
205

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

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

    
214
    if not account:
215
        account_exists = False
216

    
217
    filter_extra = {}
218
    if not show_deleted:
219
        filter_extra['deleted'] = False
220

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

    
233
    if vms.count() == 0 and private_networks.count() == 0:
234
        account_exists = False
235

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

    
251
    return direct_to_template(request, "helpdesk/account.html",
252
                              extra_context=user_context)
253

    
254

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

    
265

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

    
276

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

    
287

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