Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / helpdesk / views.py @ 468ba00b

History | View | Annotate | Download (9.7 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
from snf_django.lib.astakos import get_user
47
from synnefo.db.models import VirtualMachine, NetworkInterface, Network
48
from astakosclient import AstakosClient
49

    
50
# server actions specific imports
51
from synnefo.api import servers
52
from synnefo.logic import backend as servers_backend
53

    
54
logger = logging.getLogger(__name__)
55

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

    
60

    
61
def get_token_from_cookie(request, cookiename):
62
    """
63
    Extract token from the cookie name provided. Cookie should be in the same
64
    form as astakos service sets its cookie contents::
65

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

    
74
    return None
75

    
76

    
77
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME',
78
                           getattr(settings, 'UI_AUTH_COOKIE_NAME',
79
                                   '_pithos2_a'))
80
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS', ['helpdesk'])
81
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)
82

    
83

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

    
92
        token = request.POST.get('token', None)
93
        if token and token == request.user.get('auth_token', None):
94
            return func(request, *args, **kwargs)
95

    
96
        raise PermissionDenied
97

    
98
    return wrapper
99

    
100

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

    
111
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
112
        get_user(request, settings.ASTAKOS_URL,
113
                 fallback_token=token, logger=logger)
114
        if hasattr(request, 'user') and request.user:
115
            groups = request.user.get('groups', [])
116

    
117
            if not groups:
118
                logger.error("Failed to access helpdesk view %r",
119
                             request.user_uniq)
120
                raise PermissionDenied
121

    
122
            has_perm = False
123
            for g in groups:
124
                if g in permitted_groups:
125
                    has_perm = True
126

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

    
137
        logging.info("User %s accessed helpdesk view (%s)", request.user_uniq,
138
                     request.path)
139
        return func(request, *args, **kwargs)
140

    
141
    return wrapper
142

    
143

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

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

    
158

    
159
@helpdesk_user_required
160
def account(request, search_query):
161
    """
162
    Account details view.
163
    """
164

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

    
168
    account_exists = True
169
    vms = []
170
    networks = []
171
    is_ip = IP_SEARCH_REGEX.match(search_query)
172
    is_uuid = UUID_SEARCH_REGEX.match(search_query)
173
    is_vm = VM_SEARCH_REGEX.match(search_query)
174
    account_name = search_query
175
    auth_token = request.user.get('auth_token')
176

    
177
    if is_ip:
178
        try:
179
            nic = NetworkInterface.objects.get(ipv4=search_query)
180
            search_query = nic.machine.userid
181
            is_uuid = True
182
        except NetworkInterface.DoesNotExist:
183
            account_exists = False
184
            account = None
185

    
186
    if is_vm:
187
        vmid = is_vm.groupdict().get('vmid')
188
        try:
189
            vm = VirtualMachine.objects.get(pk=int(vmid))
190
            search_query = vm.userid
191
            is_uuid = True
192
        except VirtualMachine.DoesNotExist:
193
            account_exists = False
194
            account = None
195
            search_query = vmid
196

    
197
    astakos = AstakosClient(settings.ASTAKOS_URL, retry=2,
198
                            use_pool=True, logger=logger)
199

    
200
    if is_uuid:
201
        account = search_query
202
        account_name = astakos.get_username(auth_token, account)
203

    
204
    if account_exists and not is_uuid:
205
        account_name = search_query
206
        account = astakos.get_uuid(auth_token, account_name)
207

    
208
    if not account:
209
        account_exists = False
210

    
211
    filter_extra = {}
212
    if not show_deleted:
213
        filter_extra['deleted'] = False
214

    
215
    # all user vms
216
    vms = VirtualMachine.objects.filter(userid=account,
217
                                        **filter_extra).order_by('deleted')
218
    # return all user private and public networks
219
    public_networks = Network.objects.filter(public=True,
220
                                             nics__machine__userid=account,
221
                                             **filter_extra
222
                                             ).order_by('state').distinct()
223
    private_networks = Network.objects.filter(userid=account,
224
                                              **filter_extra).order_by('state')
225
    networks = list(public_networks) + list(private_networks)
226

    
227
    if vms.count() == 0 and private_networks.count() == 0:
228
        account_exists = False
229

    
230
    user_context = {
231
        'account_exists': account_exists,
232
        'is_ip': is_ip,
233
        'is_vm': is_vm,
234
        'is_uuid': is_uuid,
235
        'account': account,
236
        'search_query': search_query,
237
        'vms': vms,
238
        'show_deleted': show_deleted,
239
        'account_name': account_name,
240
        'token': request.user['auth_token'],
241
        'networks': networks,
242
        'UI_MEDIA_URL': settings.UI_MEDIA_URL
243
    }
244

    
245
    return direct_to_template(request, "helpdesk/account.html",
246
                              extra_context=user_context)
247

    
248

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

    
259

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

    
270

    
271
@helpdesk_user_required
272
@token_check
273
def vm_shutdown(request, vm_id):
274
    logging.info("VM %s shutdown by %s", vm_id, request.user_uniq)
275
    vm = VirtualMachine.objects.get(pk=vm_id)
276
    servers.start_action(vm, 'STOP')
277
    servers_backend.shutdown_instance(vm)
278
    account = vm.userid
279
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
280

    
281

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