Statistics
| Branch: | Tag: | Revision:

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

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

    
50
logger = logging.getLogger(__name__)
51

    
52
IP_SEARCH_REGEX = re.compile('([0-9]+)(?:\.[0-9]+){3}')
53
UUID_SEARCH_REGEX = re.compile('([0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12})')
54
VM_SEARCH_REGEX = re.compile('vm(-){0,}(?P<vmid>[0-9]+)')
55

    
56

    
57

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

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

    
71
    return None
72

    
73

    
74
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME',
75
                           getattr(settings, 'UI_AUTH_COOKIE_NAME',
76
                                   '_pithos2_a'))
77
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS', ['helpdesk'])
78
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)
79

    
80
# guess cyclades setting too
81
USER_CATALOG_URL = getattr(settings, 'CYCLADES_USER_CATALOG_URL', None)
82
USER_CATALOG_URL = getattr(settings, 'HELPDESK_USER_CATALOG_URL',
83
                           USER_CATALOG_URL)
84

    
85

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

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

    
98
        raise PermissionDenied
99

    
100
    return wrapper
101

    
102

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

    
113
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
114
        get_user(request, settings.ASTAKOS_URL, fallback_token=token)
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 %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.")
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
    if is_uuid:
199
        account = search_query
200
        account_name = astakos.get_displayname(auth_token, account,
201
                                               USER_CATALOG_URL)
202

    
203
    if account_exists and not is_uuid:
204
        account_name = search_query
205
        account = astakos.get_user_uuid(auth_token, account_name,
206
                                        USER_CATALOG_URL)
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
        'csrf_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 suspend_vm(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 suspend_vm_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,)))