Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.8 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
IP_SEARCH_REGEX = re.compile('([0-9]+)(?:\.[0-9]+){3}')
58
UUID_SEARCH_REGEX = re.compile('([0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12})')
59
VM_SEARCH_REGEX = re.compile('vm(-){0,}(?P<vmid>[0-9]+)')
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

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

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

    
97
        raise PermissionDenied
98

    
99
    return wrapper
100

    
101

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

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

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

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

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

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

    
142
    return wrapper
143

    
144

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

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

    
159

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

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

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

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

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

    
198
    astakos_client = astakosclient.AstakosClient(settings.ASTAKOS_URL, retry=2,
199
                                                 use_pool=True, logger=logger)
200

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

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

    
209
    if not account:
210
        account_exists = False
211

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

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

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

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

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

    
249

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

    
260

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

    
271

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

    
282

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