Revision 605d23bf

b/snf-cyclades-app/synnefo/app_settings/default/helpdesk.py
4 4
# Which is the cookie name that stores the token, leave it commented out
5 5
# to use same value as UI_AUTH_COOKIE_NAME value.
6 6
#HELPDESK_AUTH_COOKIE_NAME = UI_AUTH_COOKIE_NAME
7

  
8
# Astakos groups which have access to helpdesk views
9
#HELPDESK_PERMITTED_GROUPS = ['helpdesk']
b/snf-cyclades-app/synnefo/helpdesk/static/css/extra.css
185 185

  
186 186
h4 i 							{ margin-top: 4px; margin-right: 10px;}
187 187
.container-fluid 				{ margin-left:auto; margin-right:auto; max-width:960px; }
188
h3.info							{ cursor:default; color:#2956B2; text-align:center; font-size:16px; margin-bottom:0;}
188
h3.info							{ cursor:default; color:#2956B2; text-align:center; font-size:16px; margin-bottom:0;}
189

  
190
.vm-suspend-form { margin-top: 20px; text-align: right; }
191
.vm-suspend-form form { margin-bottom: 0px; }
192
.vm-suspend-form.suspended form input { background-color: #2956B2; color: #ffffff;}
193
.vm-suspend-form form input { background-color: #F81A23; color: #ffffff;}
b/snf-cyclades-app/synnefo/helpdesk/templates/helpdesk/_suspend.html
1
{% if vm.suspended %}
2
<form method="post" action="{% url helpdesk-suspend-vm-release vm_id=vm.pk %}">
3
  <input type="hidden" name="token" value="{{ csrf_token }}" />
4
  <input type="submit" value="RELEASE SUSPENSION" />
5
</form>
6
{% else %}
7
<form method="post" action="{% url helpdesk-suspend-vm vm_id=vm.pk %}">
8
  <input type="hidden" name="token" value="{{ csrf_token }}" />
9
  <input type="submit" value="SUSPEND" />
10
</form>
11
{% endif %}
b/snf-cyclades-app/synnefo/helpdesk/templates/helpdesk/vms_list.html
1 1
{% load helpdesk_tags %}
2 2
<div class="object-anchor" id="vm-{{vm.pk}}"></div>
3 3
<div class="vm-details object-details {{ rowcls }}">
4
    <h4><em><img src="{{ UI_MEDIA_URL }}images/icons/os/{{ vm|get_os }}.png" />{{ vm|get_os }}</em><i class="icon-tasks"></i>{{ vm.name }}<span class="badge">&nbsp;</span></h4>
5
    {{ vm|vm_status_badge|safe }}
6
    <span class="badge badge-inverse">ID: {{ vm.pk }}</span>
7
    <span class="badge badge-inverse">{{ vm|vm_public_ip }}</span>
8
    <span class="badge badge-inverse flavor">
9
        <span class="cpu">{{ vm.flavor.cpu }}x</span>
10
        <span class="ram">{{ vm.flavor.ram}}MB</span>
11
        <span class="disk">{{ vm.flavor.disk }}GB</span>
4
  <h4><em><img src="{{ UI_MEDIA_URL }}images/icons/os/{{ vm|get_os }}.png" />{{ vm|get_os }}</em><i class="icon-tasks"></i>{{ vm.name }}<span class="badge">&nbsp;</span></h4>
5
  {{ vm|vm_status_badge|safe }}
6
  <span class="badge badge-inverse">ID: {{ vm.pk }}</span>
7
  <span class="badge badge-inverse">{{ vm|vm_public_ip }}</span>
8
  {% if vm.suspended %}
9
  <span class="badge badge-important">SUSPENDED</span>
10
  {% endif %}
11
  <span class="badge badge-inverse flavor">
12
    <span class="cpu">{{ vm.flavor.cpu }}x</span>
13
    <span class="ram">{{ vm.flavor.ram}}MB</span>
14
    <span class="disk">{{ vm.flavor.disk }}GB</span>
15
  </span>
16
  <div class="vm-details-content object-details-content">
12 17

  
13
    </span>
14
    <div class="vm-details-content object-details-content">
15
        
16
        <ul class="nav nav-tabs">
17
		    <li class="active"><a href="#details{{ vm.pk }}" data-toggle="tab">Details</a></li>
18
		    <li><a href="#metadata{{ vm.pk }}" data-toggle="tab">Metadata</a></li>
19
		    <li><a href="#backend{{ vm.pk }}" data-toggle="tab">Backend info</a></li>
20
		    <li><a href="#network{{ vm.pk }}" data-toggle="tab">Network interfaces</a></li>
21
	    </ul>
22
	    <div class="tab-content">
23
			<div class="tab-pane active" id="details{{ vm.pk }}">
24
				<dl class="dl-horizontal well">
25
		            <dt>ID</dt><dd>{{ vm.pk }}</dd>
26
		            <dt>Name</dt><dd>{{ vm.name }}</dd>
27
		            <dt>User id</dt><dd>{{ vm.userid }}</dd>
28
		            <dt>Created</dt><dd>{{ vm.created }} ({{ vm.created|timesince }} <strong>ago</strong>)</dd>
29
		            <dt>Updated</dt><dd>{{ vm.updated }} ({{ vm.updated|timesince }} <strong>ago</strong>)</dd>
30
		            <dt>Suspended</dt><dd>{{ vm.suspended }}</dd>
31
		            <dt>Deleted</dt><dd>{{ vm.deleted }}</dd>
32
		            <dt>Image id</dt><dd>{{ vm.imageid }}</dd>
33
		            <dt>Flavor</dt><dd>{{ vm.flavor.cpu }},
34
		                        {{ vm.flavor.disk }},
35
		                        {{ vm.flavor.ram }},
36
		                        {{ vm.flavor.disk_template }}</dd>
37
		        </dl>
38
			</div>
39
			<div class="tab-pane" id="metadata{{ vm.pk }}">
40
				<dl class="dl-horizontal well">
41
		            {% for meta in vm.metadata.all %}  
42
			            <dt>{{ meta.meta_key }}</dt><dd>{{ meta.meta_value }}</dd>
43
		            {% empty %}
44
		            <dt>No metadata</dt>
45
		            {% endfor %}
46
		        </dl>
47
			</div>
48
			<div class="tab-pane" id="backend{{ vm.pk }}">
49
				<dl class="dl-horizontal well">
50
		            <dt>Action</dt><dd>{{ vm.get_action_display }} ({{ vm.action }})</dd>
51
		            <dt>Operstate</dt><dd>{{ vm.get_operstate_display }} ({{ vm.operstate }})</dd>
52
		            <dt>Backend job id</dt><dd>{{ vm.backendjobid }}</dd>
53
		            <dt>Backend op code</dt><dd>{{ vm.get_backendopcode_display }} ({{ vm.backendopcode }})</dd>
54
		            <dt>Backend log msg</dt><dd>{{ vm.backendlogmsg }}</dd>
55
		            <dt>Build backendjobstatus</dt><dd>{{ vm.backendjobstatus }}</dd>
56
		            <dt>Build percentage</dt><dd>{{ vm.buildpercentage }}</dd>
57
		        </dl>
58
			</div>
59
			<div class="tab-pane" id="network{{ vm.pk }}">
60
				<table class="table well">
61
		            <thead>
62
		                <td>ID</td>
63
		                <td>Network (ID)</td>
64
		                <td>Created</td>
65
		                <td>Updated</td>
66
		                <td>Index</td>
67
		                <td>MAC</td>
68
		                <td>IPv4</td>
69
		                <td>IPv6</td>
70
		                <td>Firewall</td>
71
		            </thead>
72
		            <tbody>
73
		                {% for nic in vm.nics.all %}
74
		                <tr>
75
		                    <td>{{ nic.pk }}</td>
76
		                    <td>{{ nic.network }} ({{ nic.network.pk }})</td>
77
		                    <td>{{ nic.created }}</td>
78
		                    <td>{{ nic.updated }}</td>
79
		                    <td>{{ nic.index }}</td>
80
		                    <td>{{ nic.mac }}</td>
81
		                    <td>{{ nic.ipv4 }}</td>
82
		                    <td>{{ nic.ipv6 }}</td>
83
		                    <td>{{ nic.get_firewall_profile_display }} ({{nic.firewall_profile}})</td>
84
		                </tr>
85
		                {% empty %}
86
		                <tr>
87
		                    <td colspan=9>No network interface available</td>
88
		                </tr>
89
		                {% endfor %}
90
		            </tbody>
91
		        </table>
92
			</div>
93
		</div>        
94
    </div>
18
    <ul class="nav nav-tabs">
19
      <li class="active"><a href="#details{{ vm.pk }}" data-toggle="tab">Details</a></li>
20
      <li><a href="#metadata{{ vm.pk }}" data-toggle="tab">Metadata</a></li>
21
      <li><a href="#backend{{ vm.pk }}" data-toggle="tab">Backend info</a></li>
22
      <li><a href="#network{{ vm.pk }}" data-toggle="tab">Network interfaces</a></li>
23
    </ul>
24
    <div class="tab-content">
25
      <div class="tab-pane active" id="details{{ vm.pk }}">
26
        <dl class="dl-horizontal well">
27
          <dt>ID</dt><dd>{{ vm.pk }}</dd>
28
          <dt>Name</dt><dd>{{ vm.name }}</dd>
29
          <dt>User id</dt><dd>{{ vm.userid }}</dd>
30
          <dt>Created</dt><dd>{{ vm.created }} ({{ vm.created|timesince }} <strong>ago</strong>)</dd>
31
          <dt>Updated</dt><dd>{{ vm.updated }} ({{ vm.updated|timesince }} <strong>ago</strong>)</dd>
32
          <dt>Suspended</dt><dd>{{ vm.suspended }}</dd>
33
          <dt>Deleted</dt><dd>{{ vm.deleted }}</dd>
34
          <dt>Image id</dt><dd>{{ vm.imageid }}</dd>
35
          <dt>Flavor</dt><dd>{{ vm.flavor.cpu }},
36
          {{ vm.flavor.disk }},
37
          {{ vm.flavor.ram }},
38
          {{ vm.flavor.disk_template }}</dd>
39
        </dl>
40
      </div>
41
      <div class="tab-pane" id="metadata{{ vm.pk }}">
42
        <dl class="dl-horizontal well">
43
          {% for meta in vm.metadata.all %}  
44
          <dt>{{ meta.meta_key }}</dt><dd>{{ meta.meta_value }}</dd>
45
          {% empty %}
46
          <dt>No metadata</dt>
47
          {% endfor %}
48
        </dl>
49
      </div>
50
      <div class="tab-pane" id="backend{{ vm.pk }}">
51
        <dl class="dl-horizontal well">
52
          <dt>Action</dt><dd>{{ vm.get_action_display }} ({{ vm.action }})</dd>
53
          <dt>Operstate</dt><dd>{{ vm.get_operstate_display }} ({{ vm.operstate }})</dd>
54
          <dt>Backend job id</dt><dd>{{ vm.backendjobid }}</dd>
55
          <dt>Backend op code</dt><dd>{{ vm.get_backendopcode_display }} ({{ vm.backendopcode }})</dd>
56
          <dt>Backend log msg</dt><dd>{{ vm.backendlogmsg }}</dd>
57
          <dt>Build backendjobstatus</dt><dd>{{ vm.backendjobstatus }}</dd>
58
          <dt>Build percentage</dt><dd>{{ vm.buildpercentage }}</dd>
59
        </dl>
60
      </div>
61
      <div class="tab-pane" id="network{{ vm.pk }}">
62
        <table class="table well">
63
          <thead>
64
            <td>ID</td>
65
            <td>Network (ID)</td>
66
            <td>Created</td>
67
            <td>Updated</td>
68
            <td>Index</td>
69
            <td>MAC</td>
70
            <td>IPv4</td>
71
            <td>IPv6</td>
72
            <td>Firewall</td>
73
          </thead>
74
          <tbody>
75
            {% for nic in vm.nics.all %}
76
            <tr>
77
              <td>{{ nic.pk }}</td>
78
              <td>{{ nic.network }} ({{ nic.network.pk }})</td>
79
              <td>{{ nic.created }}</td>
80
              <td>{{ nic.updated }}</td>
81
              <td>{{ nic.index }}</td>
82
              <td>{{ nic.mac }}</td>
83
              <td>{{ nic.ipv4 }}</td>
84
              <td>{{ nic.ipv6 }}</td>
85
              <td>{{ nic.get_firewall_profile_display }} ({{nic.firewall_profile}})</td>
86
            </tr>
87
            {% empty %}
88
            <tr>
89
              <td colspan=9>No network interface available</td>
90
            </tr>
91
            {% endfor %}
92
          </tbody>
93
        </table>
94
      </div>
95
    </div>        
96
  </div>
97
  <div class="vm-suspend-form {% if vm.suspended %}suspended{% endif %}">
98
    {% include "helpdesk/_suspend.html" %}
99
  </div>
95 100
</div>
b/snf-cyclades-app/synnefo/helpdesk/templatetags/helpdesk_tags.py
43 43
    Return a span badge styled based on the vm current status
44 44
    """
45 45
    deleted_badge = ""
46
    if network.state == "DELETED":
46
    if network.deleted:
47 47
        deleted_badge = '<span class="badge badge-important">Deleted</span>'
48 48
    return deleted_badge
49 49

  
......
55 55
        return vm.metadata.filter(meta_key="OS").get().meta_value
56 56
    except:
57 57
        return "unknown"
58
    
58

  
59 59
get_os.is_safe = True
60 60

  
61 61
@register.filter(name="network_vms")
......
64 64
    for nic in network.nics.filter(machine__userid=account):
65 65
        vms.append(nic.machine)
66 66
    return vms
67
    
67

  
68 68
network_vms.is_safe = True
69 69

  
70 70
@register.filter(name="network_nics")
......
73 73
    for nic in network.nics.filter(machine__userid=account):
74 74
        vms.append(nic)
75 75
    return vms
76
    
77
network_nics.is_safe = True
76

  
77
network_nics.is_safe = True
b/snf-cyclades-app/synnefo/helpdesk/urls.py
2 2

  
3 3
urlpatterns = patterns('',
4 4
    url(r'^$', 'synnefo.helpdesk.views.index', name='helpdesk-index'),
5
    url(r'^suspend/(?P<vm_id>[0-9]+)$', 'synnefo.helpdesk.views.suspend_vm',
6
        name='helpdesk-suspend-vm'),
7
    url(r'^suspend_release/(?P<vm_id>[0-9]+)$',
8
        'synnefo.helpdesk.views.suspend_vm_release',
9
        name='helpdesk-suspend-vm-release'),
5 10
    url(r'^api/users', 'synnefo.helpdesk.views.user_list',
6 11
        name='helpdesk-userslist'),
7 12
    url(r'^(?P<account_or_ip>.*)$', 'synnefo.helpdesk.views.account',
b/snf-cyclades-app/synnefo/helpdesk/views.py
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

  
1 34
import re
35
import logging
2 36

  
3 37
from itertools import chain
4 38

  
......
8 42
from django.conf import settings
9 43
from django.core.exceptions import PermissionDenied
10 44
from django.db.models import Q
11
from django.http import Http404, HttpResponse
45
from django.http import Http404, HttpResponse, HttpResponseRedirect
12 46
from django.utils import simplejson as json
47
from django.core.urlresolvers import reverse
48

  
13 49
from urllib import unquote
14 50

  
15 51
from synnefo.lib.astakos import get_user
16 52
from synnefo.db.models import *
17 53

  
54
logger = logging.getLogger(__name__)
55

  
18 56
IP_SEARCH_REGEX = re.compile('([0-9]+)(?:\.[0-9]+){3}')
19 57

  
20 58
def get_token_from_cookie(request, cookiename):
......
33 71
    return None
34 72

  
35 73

  
36
# TODO: here we mix ui setting with helpdesk settings
37
# if sometime in the future helpdesk gets splitted from the
38
# cyclades api code this should change and helpdesk should provide
39
# its own setting HELPDESK_AUTH_COOKIE_NAME.
40
AUTH_COOKIE = getattr(settings, 'UI_AUTH_COOKIE_NAME', getattr(settings,
41
    'HELPDESK_AUTH_COOKIE_NAME', '_pithos2_a'))
74
AUTH_COOKIE_NAME = getattr(settings, 'HELPDESK_AUTH_COOKIE_NAME', getattr(settings,
75
    'UI_AUTH_COOKIE_NAME', '_pithos2_a'))
76
PERMITTED_GROUPS = getattr(settings, 'HELPDESK_PERMITTED_GROUPS',
77
                                    ['helpdesk'])
78
SHOW_DELETED_VMS = getattr(settings, 'HELPDESK_SHOW_DELETED_VMS', False)
79

  
80

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

  
89
        token = request.POST.get('token', None)
90
        if token and token != request.user.get('auth_token', None):
91
            return func(request, *args, **kwargs)
92

  
93
        raise PermissionDenied
94

  
95
    return wrapper
42 96

  
43 97

  
44
def helpdesk_user_required(func, groups=['helpdesk']):
98
def helpdesk_user_required(func, permitted_groups=PERMITTED_GROUPS):
45 99
    """
46 100
    Django view wrapper that checks if identified request user has helpdesk
47 101
    permissions (exists in helpdesk group)
......
51 105
        if not HELPDESK_ENABLED:
52 106
            raise Http404
53 107

  
54
        token = get_token_from_cookie(request, AUTH_COOKIE)
108
        token = get_token_from_cookie(request, AUTH_COOKIE_NAME)
55 109
        get_user(request, settings.ASTAKOS_URL, fallback_token=token)
56 110
        if hasattr(request, 'user') and request.user:
57 111
            groups = request.user.get('groups', [])
......
59 113
            if not groups:
60 114
                raise PermissionDenied
61 115

  
116
            has_perm = False
62 117
            for g in groups:
63
                if not g in groups:
64
                    raise PermissionDenied
118
                if g in permitted_groups:
119
                    has_perm = True
120

  
121
            if not has_perm:
122
                raise PermissionDenied
65 123
        else:
66 124
            raise PermissionDenied
67 125

  
126
        logging.debug("User %s accessed helpdesk view" % (request.user_uniq))
68 127
        return func(request, *args, **kwargs)
69 128

  
70 129
    return wrapper
......
91 150
    Account details view.
92 151
    """
93 152

  
153
    show_deleted = bool(int(request.GET.get('deleted', SHOW_DELETED_VMS)))
154

  
94 155
    account_exists = True
95 156
    vms = []
96 157
    networks = []
......
104 165
        except NetworkInterface.DoesNotExist:
105 166
            account_exists = False
106 167
    else:
107
        # all user vms
108
        vms = VirtualMachine.objects.filter(userid=account).order_by('deleted')
168
        filter_extra = {}
169
        if not show_deleted:
170
            filter_extra['deleted'] = False
109 171

  
172
        # all user vms
173
        vms = VirtualMachine.objects.filter(userid=account,
174
                                            **filter_extra).order_by('deleted')
110 175
        # return all user private and public networks
111
        public_networks = Network.objects.filter(public=True).order_by('state')
112
        private_networks = Network.objects.filter(userid=account).order_by('state')
176
        public_networks = Network.objects.filter(public=True,
177
                                                 **filter_extra).order_by('state')
178
        private_networks = Network.objects.filter(userid=account,
179
                                                 **filter_extra).order_by('state')
113 180
        networks = list(public_networks) + list(private_networks)
114 181

  
115 182
        if vms.count() == 0 and private_networks.count() == 0:
......
120 187
        'is_ip': is_ip,
121 188
        'account': account,
122 189
        'vms': vms,
190
        'csrf_token': request.user['auth_token'],
123 191
        'networks': networks,
124 192
        'UI_MEDIA_URL': settings.UI_MEDIA_URL
125 193
    }
......
129 197

  
130 198

  
131 199
@helpdesk_user_required
200
@token_check
201
def suspend_vm(request, vm_id):
202
    vm = VirtualMachine.objects.get(pk=vm_id)
203
    vm.suspended = True
204
    vm.save()
205
    account = vm.userid
206
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
207

  
208

  
209
@helpdesk_user_required
210
@token_check
211
def suspend_vm_release(request, vm_id):
212
    vm = VirtualMachine.objects.get(pk=vm_id)
213
    vm.suspended = False
214
    vm.save()
215
    account = vm.userid
216
    return HttpResponseRedirect(reverse('helpdesk-details', args=(account,)))
217

  
218

  
219
@helpdesk_user_required
132 220
def user_list(request):
133 221
    """
134 222
    Return a json list of users based on the prefix provided. Prefix

Also available in: Unified diff