Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.9 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 snf_django.lib import astakos
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

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

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

    
75
    return None
76

    
77

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

    
84
# guess cyclades setting too
85
USER_CATALOG_URL = getattr(settings, 'CYCLADES_USER_CATALOG_URL', None)
86
USER_CATALOG_URL = getattr(settings, 'HELPDESK_USER_CATALOG_URL',
87
                           USER_CATALOG_URL)
88

    
89

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

    
98
        token = request.POST.get('token', None)
99
        if token and token == request.user.get('auth_token', None):
100
            return func(request, *args, **kwargs)
101

    
102
        raise PermissionDenied
103

    
104
    return wrapper
105

    
106

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

    
117
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
118
        get_user(request, settings.ASTAKOS_URL, fallback_token=token)
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 %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.")
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

    
163

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

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

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

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

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

    
202
    if is_uuid:
203
        account = search_query
204
        account_name = astakos.get_displayname(auth_token, account,
205
                                               USER_CATALOG_URL)
206

    
207
    if account_exists and not is_uuid:
208
        account_name = search_query
209
        account = astakos.get_user_uuid(auth_token, account_name,
210
                                        USER_CATALOG_URL)
211

    
212
    if not account:
213
        account_exists = False
214

    
215
    filter_extra = {}
216
    if not show_deleted:
217
        filter_extra['deleted'] = False
218

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

    
231
    if vms.count() == 0 and private_networks.count() == 0:
232
        account_exists = False
233

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

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

    
252

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

    
263

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

    
274

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

    
285

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