Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.1 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_BASE_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_BASE_URL,
204
                                                 retry=2, use_pool=True,
205
                                                 logger=logger)
206

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

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

    
215
    if not account:
216
        account_exists = False
217

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

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

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

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

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

    
255

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

    
266

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

    
277

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

    
288

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